From d25a97fe178a600c311a15d1ca99aaa3e967293b Mon Sep 17 00:00:00 2001 From: Nawaz Dhandala Date: Sun, 15 Feb 2026 11:47:32 +0000 Subject: [PATCH] Refactor components for improved readability and consistency - Added missing newlines at the end of files in MarkdownContent.tsx and RootCauseCard.tsx - Reformatted shadowColor and color properties in NotesSection.tsx, SegmentedControl.tsx, MainTabNavigator.tsx, HomeScreen.tsx for better readability - Enhanced code formatting in SectionHeader.tsx and OnCallStackNavigator.tsx for consistency - Improved readability of getEntityId function in useAllProjectOnCallPolicies.ts - Refactored conditional rendering in AlertDetailScreen.tsx, AlertEpisodeDetailScreen.tsx, IncidentDetailScreen.tsx, and IncidentEpisodeDetailScreen.tsx for better clarity --- CLI/Commands/ConfigCommands.ts | 39 ++-- CLI/Commands/ResourceCommands.ts | 101 ++++------- CLI/Commands/UtilityCommands.ts | 47 +++-- CLI/Core/ApiClient.ts | 4 +- CLI/Core/ConfigManager.ts | 10 +- CLI/Core/OutputFormatter.ts | 29 +-- CLI/Index.ts | 4 +- CLI/Tests/ApiClient.test.ts | 167 ++++++++++++------ CLI/Tests/ConfigCommands.test.ts | 49 ++--- CLI/Tests/ConfigManager.test.ts | 104 +++++++---- CLI/Tests/ErrorHandler.test.ts | 16 +- CLI/Tests/Index.test.ts | 18 +- CLI/Tests/OutputFormatter.test.ts | 132 +++++++------- CLI/Tests/ResourceCommands.test.ts | 129 +++++++++----- CLI/Tests/SelectFieldGenerator.test.ts | 113 ++++++++---- CLI/Tests/UtilityCommands.test.ts | 45 ++--- CLI/Utils/SelectFieldGenerator.ts | 13 +- .../src/Pages/Monitor/DisabledMonitors.tsx | 4 +- .../Pages/Monitor/NotOperationalMonitors.tsx | 4 +- Dashboard/src/Pages/Monitor/ProbeDisabled.tsx | 4 +- .../src/Pages/Monitor/ProbeDisconnected.tsx | 4 +- .../UserSettings/IncomingCallPhoneNumbers.tsx | 4 +- MobileApp/src/api/types.ts | 48 ++++- MobileApp/src/components/AlertCard.tsx | 5 +- MobileApp/src/components/EpisodeCard.tsx | 8 +- MobileApp/src/components/IncidentCard.tsx | 5 +- MobileApp/src/components/Logo.tsx | 4 +- MobileApp/src/components/MarkdownContent.tsx | 2 +- MobileApp/src/components/NotesSection.tsx | 4 +- MobileApp/src/components/RootCauseCard.tsx | 2 +- MobileApp/src/components/SectionHeader.tsx | 6 +- MobileApp/src/components/SegmentedControl.tsx | 4 +- .../src/hooks/useAllProjectOnCallPolicies.ts | 34 ++-- MobileApp/src/navigation/MainTabNavigator.tsx | 4 +- .../src/navigation/OnCallStackNavigator.tsx | 5 +- MobileApp/src/screens/AlertDetailScreen.tsx | 148 ++++++++-------- .../src/screens/AlertEpisodeDetailScreen.tsx | 136 +++++++------- MobileApp/src/screens/HomeScreen.tsx | 9 +- .../src/screens/IncidentDetailScreen.tsx | 148 ++++++++-------- .../screens/IncidentEpisodeDetailScreen.tsx | 136 +++++++------- 40 files changed, 1016 insertions(+), 732 deletions(-) diff --git a/CLI/Commands/ConfigCommands.ts b/CLI/Commands/ConfigCommands.ts index 1364d15619..4b4cca2e73 100644 --- a/CLI/Commands/ConfigCommands.ts +++ b/CLI/Commands/ConfigCommands.ts @@ -11,14 +11,17 @@ export function registerConfigCommands(program: Command): void { .command("login") .description("Authenticate with a OneUptime instance") .argument("", "API key for authentication") - .argument("", "OneUptime instance URL (e.g. https://oneuptime.com)") - .option( - "--context-name ", - "Name for this context", - "default", + .argument( + "", + "OneUptime instance URL (e.g. https://oneuptime.com)", ) + .option("--context-name ", "Name for this context", "default") .action( - (apiKey: string, instanceUrl: string, options: { contextName: string }) => { + ( + apiKey: string, + instanceUrl: string, + options: { contextName: string }, + ) => { try { const context: CLIContext = { name: options.contextName, @@ -68,20 +71,17 @@ export function registerConfigCommands(program: Command): void { process.argv.includes("--no-color"); const table: Table.Table = new Table({ - head: ["", "Name", "URL"].map((h: string) => - noColor ? h : chalk.cyan(h), - ), + head: ["", "Name", "URL"].map((h: string) => { + return noColor ? h : chalk.cyan(h); + }), style: { head: [], border: [] }, }); for (const ctx of contexts) { - table.push([ - ctx.isCurrent ? "*" : "", - ctx.name, - ctx.apiUrl, - ]); + table.push([ctx.isCurrent ? "*" : "", ctx.name, ctx.apiUrl]); } + // eslint-disable-next-line no-console console.log(table.toString()); }); @@ -93,9 +93,7 @@ export function registerConfigCommands(program: Command): void { ConfigManager.setCurrentContext(name); printSuccess(`Switched to context "${name}".`); } catch (error) { - printError( - error instanceof Error ? error.message : String(error), - ); + printError(error instanceof Error ? error.message : String(error)); process.exit(1); } }); @@ -119,8 +117,11 @@ export function registerConfigCommands(program: Command): void { ctx.apiKey.substring(ctx.apiKey.length - 4) : "****"; + // eslint-disable-next-line no-console console.log(`Context: ${ctx.name}`); + // eslint-disable-next-line no-console console.log(`URL: ${ctx.apiUrl}`); + // eslint-disable-next-line no-console console.log(`API Key: ${maskedKey}`); }); @@ -132,9 +133,7 @@ export function registerConfigCommands(program: Command): void { ConfigManager.removeContext(name); printSuccess(`Context "${name}" deleted.`); } catch (error) { - printError( - error instanceof Error ? error.message : String(error), - ); + printError(error instanceof Error ? error.message : String(error)); process.exit(1); } }); diff --git a/CLI/Commands/ResourceCommands.ts b/CLI/Commands/ResourceCommands.ts index 0c31f76447..ae0c007048 100644 --- a/CLI/Commands/ResourceCommands.ts +++ b/CLI/Commands/ResourceCommands.ts @@ -3,10 +3,9 @@ import DatabaseModels from "Common/Models/DatabaseModels/Index"; import AnalyticsModels from "Common/Models/AnalyticsModels/Index"; import BaseModel from "Common/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel"; import AnalyticsBaseModel from "Common/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel"; -import { ResourceInfo } from "../Types/CLITypes"; +import { ResourceInfo, ResolvedCredentials } from "../Types/CLITypes"; import { executeApiRequest, ApiOperation } from "../Core/ApiClient"; import { CLIOptions, getResolvedCredentials } from "../Core/ConfigManager"; -import { ResolvedCredentials } from "../Types/CLITypes"; import { formatOutput, printSuccess } from "../Core/OutputFormatter"; import { handleError } from "../Core/ErrorHandler"; import { generateAllFieldsSelect } from "../Utils/SelectFieldGenerator"; @@ -118,8 +117,7 @@ function registerListCommand( }) => { try { const parentOpts: CLIOptions = getParentOptions(resourceCmd); - const creds: ResolvedCredentials = - getResolvedCredentials(parentOpts); + const creds: ResolvedCredentials = getResolvedCredentials(parentOpts); const select: JSONObject = generateAllFieldsSelect( resource.tableName, resource.modelType, @@ -130,15 +128,11 @@ function registerListCommand( apiKey: creds.apiKey, apiPath: resource.apiPath, operation: "list" as ApiOperation, - query: options.query - ? parseJsonArg(options.query) - : undefined, + query: options.query ? parseJsonArg(options.query) : undefined, select, skip: parseInt(options.skip, 10), limit: parseInt(options.limit, 10), - sort: options.sort - ? parseJsonArg(options.sort) - : undefined, + sort: options.sort ? parseJsonArg(options.sort) : undefined, }); // Extract data array from response @@ -147,6 +141,7 @@ function registerListCommand( ? ((result as JSONObject)["data"] as JSONValue) || result : result; + // eslint-disable-next-line no-console console.log(formatOutput(responseData, options.output)); } catch (error) { handleError(error); @@ -166,8 +161,7 @@ function registerGetCommand( .action(async (id: string, options: { output?: string }) => { try { const parentOpts: CLIOptions = getParentOptions(resourceCmd); - const creds: ResolvedCredentials = - getResolvedCredentials(parentOpts); + const creds: ResolvedCredentials = getResolvedCredentials(parentOpts); const select: JSONObject = generateAllFieldsSelect( resource.tableName, resource.modelType, @@ -182,6 +176,7 @@ function registerGetCommand( select, }); + // eslint-disable-next-line no-console console.log(formatOutput(result, options.output)); } catch (error) { handleError(error); @@ -200,31 +195,21 @@ function registerCreateCommand( .option("--file ", "Read resource data from a JSON file") .option("-o, --output ", "Output format: json, table, wide") .action( - async (options: { - data?: string; - file?: string; - output?: string; - }) => { + async (options: { data?: string; file?: string; output?: string }) => { try { let data: JSONObject; if (options.file) { - const fileContent: string = fs.readFileSync( - options.file, - "utf-8", - ); + const fileContent: string = fs.readFileSync(options.file, "utf-8"); data = JSON.parse(fileContent) as JSONObject; } else if (options.data) { data = parseJsonArg(options.data); } else { - throw new Error( - "Either --data or --file is required for create.", - ); + throw new Error("Either --data or --file is required for create."); } const parentOpts: CLIOptions = getParentOptions(resourceCmd); - const creds: ResolvedCredentials = - getResolvedCredentials(parentOpts); + const creds: ResolvedCredentials = getResolvedCredentials(parentOpts); const result: JSONValue = await executeApiRequest({ apiUrl: creds.apiUrl, @@ -234,6 +219,7 @@ function registerCreateCommand( data, }); + // eslint-disable-next-line no-console console.log(formatOutput(result, options.output)); } catch (error) { handleError(error); @@ -251,32 +237,27 @@ function registerUpdateCommand( .description(`Update an existing ${resource.singularName}`) .requiredOption("--data ", "Fields to update as JSON") .option("-o, --output ", "Output format: json, table, wide") - .action( - async ( - id: string, - options: { data: string; output?: string }, - ) => { - try { - const data: JSONObject = parseJsonArg(options.data); - const parentOpts: CLIOptions = getParentOptions(resourceCmd); - const creds: ResolvedCredentials = - getResolvedCredentials(parentOpts); + .action(async (id: string, options: { data: string; output?: string }) => { + try { + const data: JSONObject = parseJsonArg(options.data); + const parentOpts: CLIOptions = getParentOptions(resourceCmd); + const creds: ResolvedCredentials = getResolvedCredentials(parentOpts); - const result: JSONValue = await executeApiRequest({ - apiUrl: creds.apiUrl, - apiKey: creds.apiKey, - apiPath: resource.apiPath, - operation: "update" as ApiOperation, - id, - data, - }); + const result: JSONValue = await executeApiRequest({ + apiUrl: creds.apiUrl, + apiKey: creds.apiKey, + apiPath: resource.apiPath, + operation: "update" as ApiOperation, + id, + data, + }); - console.log(formatOutput(result, options.output)); - } catch (error) { - handleError(error); - } - }, - ); + // eslint-disable-next-line no-console + console.log(formatOutput(result, options.output)); + } catch (error) { + handleError(error); + } + }); } function registerDeleteCommand( @@ -290,8 +271,7 @@ function registerDeleteCommand( .action(async (id: string, _options: { force?: boolean }) => { try { const parentOpts: CLIOptions = getParentOptions(resourceCmd); - const creds: ResolvedCredentials = - getResolvedCredentials(parentOpts); + const creds: ResolvedCredentials = getResolvedCredentials(parentOpts); await executeApiRequest({ apiUrl: creds.apiUrl, @@ -301,9 +281,7 @@ function registerDeleteCommand( id, }); - printSuccess( - `${resource.singularName} ${id} deleted successfully.`, - ); + printSuccess(`${resource.singularName} ${id} deleted successfully.`); } catch (error) { handleError(error); } @@ -321,17 +299,14 @@ function registerCountCommand( .action(async (options: { query?: string }) => { try { const parentOpts: CLIOptions = getParentOptions(resourceCmd); - const creds: ResolvedCredentials = - getResolvedCredentials(parentOpts); + const creds: ResolvedCredentials = getResolvedCredentials(parentOpts); const result: JSONValue = await executeApiRequest({ apiUrl: creds.apiUrl, apiKey: creds.apiKey, apiPath: resource.apiPath, operation: "count" as ApiOperation, - query: options.query - ? parseJsonArg(options.query) - : undefined, + query: options.query ? parseJsonArg(options.query) : undefined, }); // Count response is typically { count: number } @@ -341,8 +316,10 @@ function registerCountCommand( !Array.isArray(result) && "count" in (result as JSONObject) ) { + // eslint-disable-next-line no-console console.log((result as JSONObject)["count"]); } else { + // eslint-disable-next-line no-console console.log(result); } } catch (error) { @@ -357,9 +334,7 @@ export function registerResourceCommands(program: Command): void { for (const resource of resources) { const resourceCmd: Command = program .command(resource.name) - .description( - `Manage ${resource.pluralName} (${resource.modelType})`, - ); + .description(`Manage ${resource.pluralName} (${resource.modelType})`); // Database models get full CRUD if (resource.modelType === "database") { diff --git a/CLI/Commands/UtilityCommands.ts b/CLI/Commands/UtilityCommands.ts index fd64e587e5..4192f608c9 100644 --- a/CLI/Commands/UtilityCommands.ts +++ b/CLI/Commands/UtilityCommands.ts @@ -1,10 +1,16 @@ import { Command } from "commander"; -import { CLIContext } from "../Types/CLITypes"; -import { getCurrentContext, CLIOptions, getResolvedCredentials } from "../Core/ConfigManager"; -import { ResolvedCredentials } from "../Types/CLITypes"; +import { + CLIContext, + ResolvedCredentials, + ResourceInfo, +} from "../Types/CLITypes"; +import { + getCurrentContext, + CLIOptions, + getResolvedCredentials, +} from "../Core/ConfigManager"; import { printInfo, printError } from "../Core/OutputFormatter"; import { discoverResources } from "./ResourceCommands"; -import { ResourceInfo } from "../Types/CLITypes"; import Table from "cli-table3"; import chalk from "chalk"; @@ -15,11 +21,15 @@ export function registerUtilityCommands(program: Command): void { .description("Print CLI version") .action(() => { try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const pkg: { version: string } = require("../package.json") as { version: string }; + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports + const pkg: { version: string } = require("../package.json") as { + version: string; + }; + // eslint-disable-next-line no-console console.log(pkg.version); } catch { // Fallback if package.json can't be loaded at runtime + // eslint-disable-next-line no-console console.log("1.0.0"); } }); @@ -42,7 +52,9 @@ export function registerUtilityCommands(program: Command): void { try { creds = getResolvedCredentials(cliOpts); } catch { - printInfo("Not authenticated. Run `oneuptime login` to authenticate."); + printInfo( + "Not authenticated. Run `oneuptime login` to authenticate.", + ); return; } @@ -53,9 +65,12 @@ export function registerUtilityCommands(program: Command): void { creds.apiKey.substring(creds.apiKey.length - 4) : "****"; + // eslint-disable-next-line no-console console.log(`URL: ${creds.apiUrl}`); + // eslint-disable-next-line no-console console.log(`API Key: ${maskedKey}`); if (ctx) { + // eslint-disable-next-line no-console console.log(`Context: ${ctx.name}`); } } catch (error) { @@ -73,7 +88,9 @@ export function registerUtilityCommands(program: Command): void { const resources: ResourceInfo[] = discoverResources(); const filtered: ResourceInfo[] = options.type - ? resources.filter((r: ResourceInfo) => r.modelType === options.type) + ? resources.filter((r: ResourceInfo) => { + return r.modelType === options.type; + }) : resources; if (filtered.length === 0) { @@ -87,16 +104,26 @@ export function registerUtilityCommands(program: Command): void { const table: Table.Table = new Table({ head: ["Command", "Singular", "Plural", "Type", "API Path"].map( - (h: string) => (noColor ? h : chalk.cyan(h)), + (h: string) => { + return noColor ? h : chalk.cyan(h); + }, ), style: { head: [], border: [] }, }); for (const r of filtered) { - table.push([r.name, r.singularName, r.pluralName, r.modelType, r.apiPath]); + table.push([ + r.name, + r.singularName, + r.pluralName, + r.modelType, + r.apiPath, + ]); } + // eslint-disable-next-line no-console console.log(table.toString()); + // eslint-disable-next-line no-console console.log(`\nTotal: ${filtered.length} resources`); }); } diff --git a/CLI/Core/ApiClient.ts b/CLI/Core/ApiClient.ts index 824a4db3ad..278ec0a486 100644 --- a/CLI/Core/ApiClient.ts +++ b/CLI/Core/ApiClient.ts @@ -126,9 +126,7 @@ export async function executeApiRequest( case "count": case "list": case "read": - response = await API.post( - data ? { ...baseOptions, data } : baseOptions, - ); + response = await API.post(data ? { ...baseOptions, data } : baseOptions); break; case "update": response = await API.put(data ? { ...baseOptions, data } : baseOptions); diff --git a/CLI/Core/ConfigManager.ts b/CLI/Core/ConfigManager.ts index d8233f84c4..8d155df98f 100644 --- a/CLI/Core/ConfigManager.ts +++ b/CLI/Core/ConfigManager.ts @@ -80,10 +80,12 @@ export function removeContext(name: string): void { export function listContexts(): Array { const config: CLIConfig = load(); return Object.values(config.contexts).map( - (ctx: CLIContext): CLIContext & { isCurrent: boolean } => ({ - ...ctx, - isCurrent: ctx.name === config.currentContext, - }), + (ctx: CLIContext): CLIContext & { isCurrent: boolean } => { + return { + ...ctx, + isCurrent: ctx.name === config.currentContext, + }; + }, ); } diff --git a/CLI/Core/OutputFormatter.ts b/CLI/Core/OutputFormatter.ts index aa0717f9a5..ebac1b94e4 100644 --- a/CLI/Core/OutputFormatter.ts +++ b/CLI/Core/OutputFormatter.ts @@ -5,8 +5,7 @@ import chalk from "chalk"; function isColorDisabled(): boolean { return ( - process.env["NO_COLOR"] !== undefined || - process.argv.includes("--no-color") + process.env["NO_COLOR"] !== undefined || process.argv.includes("--no-color") ); } @@ -69,21 +68,21 @@ function formatTable(data: JSONValue, wide: boolean): string { "createdAt", "updatedAt", ]; - const prioritized: string[] = priority.filter((col: string) => - columns.includes(col), - ); - const remaining: string[] = columns.filter( - (col: string) => !priority.includes(col), - ); + const prioritized: string[] = priority.filter((col: string) => { + return columns.includes(col); + }); + const remaining: string[] = columns.filter((col: string) => { + return !priority.includes(col); + }); columns = [...prioritized, ...remaining].slice(0, 6); } const useColor: boolean = !isColorDisabled(); const table: Table.Table = new Table({ - head: columns.map((col: string) => - useColor ? chalk.cyan(col) : col, - ), + head: columns.map((col: string) => { + return useColor ? chalk.cyan(col) : col; + }), style: { head: [], border: [], @@ -154,8 +153,10 @@ export function formatOutput(data: JSONValue, format?: string): string { export function printSuccess(message: string): void { const useColor: boolean = !isColorDisabled(); if (useColor) { + // eslint-disable-next-line no-console console.log(chalk.green(message)); } else { + // eslint-disable-next-line no-console console.log(message); } } @@ -163,8 +164,10 @@ export function printSuccess(message: string): void { export function printError(message: string): void { const useColor: boolean = !isColorDisabled(); if (useColor) { + // eslint-disable-next-line no-console console.error(chalk.red(message)); } else { + // eslint-disable-next-line no-console console.error(message); } } @@ -172,8 +175,10 @@ export function printError(message: string): void { export function printWarning(message: string): void { const useColor: boolean = !isColorDisabled(); if (useColor) { + // eslint-disable-next-line no-console console.error(chalk.yellow(message)); } else { + // eslint-disable-next-line no-console console.error(message); } } @@ -181,8 +186,10 @@ export function printWarning(message: string): void { export function printInfo(message: string): void { const useColor: boolean = !isColorDisabled(); if (useColor) { + // eslint-disable-next-line no-console console.log(chalk.blue(message)); } else { + // eslint-disable-next-line no-console console.log(message); } } diff --git a/CLI/Index.ts b/CLI/Index.ts index b16ea15bfb..59698a99b9 100644 --- a/CLI/Index.ts +++ b/CLI/Index.ts @@ -9,7 +9,9 @@ const program: Command = new Command(); program .name("oneuptime") - .description("OneUptime CLI - Manage your OneUptime resources from the command line") + .description( + "OneUptime CLI - Manage your OneUptime resources from the command line", + ) .version("1.0.0") .option("--api-key ", "API key (overrides config)") .option("--url ", "OneUptime instance URL (overrides config)") diff --git a/CLI/Tests/ApiClient.test.ts b/CLI/Tests/ApiClient.test.ts index 175940633f..23636fcaee 100644 --- a/CLI/Tests/ApiClient.test.ts +++ b/CLI/Tests/ApiClient.test.ts @@ -1,14 +1,20 @@ import { executeApiRequest, ApiRequestOptions } from "../Core/ApiClient"; import API from "Common/Utils/API"; import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse"; +import { JSONValue } from "Common/Types/JSON"; // Mock the Common/Utils/API module jest.mock("Common/Utils/API", () => { - const mockPost = jest.fn(); - const mockPut = jest.fn(); - const mockDelete = jest.fn(); + const mockPost: jest.Mock = jest.fn(); + const mockPut: jest.Mock = jest.fn(); + const mockDelete: jest.Mock = jest.fn(); - function MockAPI(protocol, hostname, _route) { + function MockAPI( + this: { protocol: string; hostname: string }, + protocol: string, + hostname: string, + _route: string, + ): void { this.protocol = protocol; this.hostname = hostname; } @@ -23,36 +29,50 @@ jest.mock("Common/Utils/API", () => { }; }); -function createSuccessResponse(data) { +function createSuccessResponse( + data: Record | Record[], +): { + data: Record | Record[]; + statusCode: number; +} { return { data, statusCode: 200 }; } -function createErrorResponse(statusCode, message) { - // HTTPErrorResponse computes `message` from `.data` via a getter. - // We create a proper prototype chain and set data to contain the message. - const resp = Object.create(HTTPErrorResponse.prototype); +function createErrorResponse( + statusCode: number, + message: string, +): HTTPErrorResponse { + /* + * HTTPErrorResponse computes `message` from `.data` via a getter. + * We create a proper prototype chain and set data to contain the message. + */ + const resp: HTTPErrorResponse = Object.create(HTTPErrorResponse.prototype); resp.statusCode = statusCode; - // HTTPResponse stores data in _jsonData and exposes it via `data` getter - // But since the prototype chain may not have full getters, we define them + /* + * HTTPResponse stores data in _jsonData and exposes it via `data` getter + * But since the prototype chain may not have full getters, we define them + */ Object.defineProperty(resp, "data", { - get: () => ({ message: message }), + get: (): { message: string } => { + return { message: message }; + }, configurable: true, }); return resp; } describe("ApiClient", () => { - let mockPost; - let mockPut; - let mockDelete; + let mockPost: jest.Mock; + let mockPut: jest.Mock; + let mockDelete: jest.Mock; beforeEach(() => { - mockPost = API.post; - mockPut = API.put; - mockDelete = API.delete; - (mockPost as any).mockReset(); - (mockPut as any).mockReset(); - (mockDelete as any).mockReset(); + mockPost = API.post as jest.Mock; + mockPut = API.put as jest.Mock; + mockDelete = API.delete as jest.Mock; + (mockPost as jest.Mock).mockReset(); + (mockPut as jest.Mock).mockReset(); + (mockDelete as jest.Mock).mockReset(); }); const baseOptions: ApiRequestOptions = { @@ -64,40 +84,46 @@ describe("ApiClient", () => { describe("create operation", () => { it("should make a POST request with data wrapped in { data: ... }", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({ _id: "123" })); + (mockPost as jest.Mock).mockResolvedValue( + createSuccessResponse({ _id: "123" }), + ); - const result = await executeApiRequest({ + const result: JSONValue = await executeApiRequest({ ...baseOptions, operation: "create", data: { name: "Test Incident" }, }); expect(mockPost).toHaveBeenCalledTimes(1); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.data).toEqual({ data: { name: "Test Incident" } }); expect(result).toEqual({ _id: "123" }); }); it("should use empty object when no data provided for create", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({ _id: "123" })); + (mockPost as jest.Mock).mockResolvedValue( + createSuccessResponse({ _id: "123" }), + ); await executeApiRequest({ ...baseOptions, operation: "create", }); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.data).toEqual({ data: {} }); }); }); describe("read operation", () => { it("should make a POST request with select and id in route", async () => { - (mockPost as any).mockResolvedValue( + (mockPost as jest.Mock).mockResolvedValue( createSuccessResponse({ _id: "abc", name: "Test" }), ); - const result = await executeApiRequest({ + const result: JSONValue = await executeApiRequest({ ...baseOptions, operation: "read", id: "abc-123", @@ -105,14 +131,15 @@ describe("ApiClient", () => { }); expect(mockPost).toHaveBeenCalledTimes(1); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("abc-123/get-item"); expect(callArgs.data).toEqual({ select: { _id: true, name: true } }); expect(result).toEqual({ _id: "abc", name: "Test" }); }); it("should use empty select when none provided", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({})); + (mockPost as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, @@ -120,19 +147,21 @@ describe("ApiClient", () => { id: "abc-123", }); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.data).toEqual({ select: {} }); }); it("should build route without id when no id provided", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({})); + (mockPost as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, operation: "read", }); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("/api/incident"); expect(callArgs.url.toString()).not.toContain("/get-item"); }); @@ -140,7 +169,9 @@ describe("ApiClient", () => { describe("list operation", () => { it("should make a POST request with query, select, skip, limit, sort", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({ data: [] })); + (mockPost as jest.Mock).mockResolvedValue( + createSuccessResponse({ data: [] }), + ); await executeApiRequest({ ...baseOptions, @@ -153,7 +184,8 @@ describe("ApiClient", () => { }); expect(mockPost).toHaveBeenCalledTimes(1); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("/get-list"); expect(callArgs.data).toEqual({ query: { status: "active" }, @@ -165,14 +197,17 @@ describe("ApiClient", () => { }); it("should use defaults when no query options provided", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({ data: [] })); + (mockPost as jest.Mock).mockResolvedValue( + createSuccessResponse({ data: [] }), + ); await executeApiRequest({ ...baseOptions, operation: "list", }); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.data).toEqual({ query: {}, select: {}, @@ -185,16 +220,19 @@ describe("ApiClient", () => { describe("count operation", () => { it("should make a POST request to /count path", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({ count: 42 })); + (mockPost as jest.Mock).mockResolvedValue( + createSuccessResponse({ count: 42 }), + ); - const result = await executeApiRequest({ + const result: JSONValue = await executeApiRequest({ ...baseOptions, operation: "count", query: { status: "active" }, }); expect(mockPost).toHaveBeenCalledTimes(1); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("/count"); expect(result).toEqual({ count: 42 }); }); @@ -202,9 +240,11 @@ describe("ApiClient", () => { describe("update operation", () => { it("should make a PUT request with data", async () => { - (mockPut as any).mockResolvedValue(createSuccessResponse({ _id: "abc" })); + (mockPut as jest.Mock).mockResolvedValue( + createSuccessResponse({ _id: "abc" }), + ); - const result = await executeApiRequest({ + const result: JSONValue = await executeApiRequest({ ...baseOptions, operation: "update", id: "abc-123", @@ -212,14 +252,15 @@ describe("ApiClient", () => { }); expect(mockPut).toHaveBeenCalledTimes(1); - const callArgs = (mockPut as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPut as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("abc-123"); expect(callArgs.data).toEqual({ data: { name: "Updated" } }); expect(result).toEqual({ _id: "abc" }); }); it("should use empty object when no data provided for update", async () => { - (mockPut as any).mockResolvedValue(createSuccessResponse({})); + (mockPut as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, @@ -227,26 +268,28 @@ describe("ApiClient", () => { id: "abc-123", }); - const callArgs = (mockPut as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPut as jest.Mock).mock.calls[0][0]; expect(callArgs.data).toEqual({ data: {} }); }); it("should build route without id when no id provided", async () => { - (mockPut as any).mockResolvedValue(createSuccessResponse({})); + (mockPut as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, operation: "update", }); - const callArgs = (mockPut as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPut as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("/api/incident"); }); }); describe("delete operation", () => { it("should make a DELETE request", async () => { - (mockDelete as any).mockResolvedValue(createSuccessResponse({})); + (mockDelete as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, @@ -255,27 +298,31 @@ describe("ApiClient", () => { }); expect(mockDelete).toHaveBeenCalledTimes(1); - const callArgs = (mockDelete as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockDelete as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("abc-123"); expect(callArgs.data).toBeUndefined(); }); it("should build route without id when no id provided", async () => { - (mockDelete as any).mockResolvedValue(createSuccessResponse({})); + (mockDelete as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, operation: "delete", }); - const callArgs = (mockDelete as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockDelete as jest.Mock).mock.calls[0][0]; expect(callArgs.url.toString()).toContain("/api/incident"); }); }); describe("error handling", () => { it("should throw on HTTPErrorResponse", async () => { - (mockPost as any).mockResolvedValue(createErrorResponse(500, "Server Error")); + (mockPost as jest.Mock).mockResolvedValue( + createErrorResponse(500, "Server Error"), + ); await expect( executeApiRequest({ ...baseOptions, operation: "create", data: {} }), @@ -283,7 +330,9 @@ describe("ApiClient", () => { }); it("should include status code in error message", async () => { - (mockPost as any).mockResolvedValue(createErrorResponse(403, "Forbidden")); + (mockPost as jest.Mock).mockResolvedValue( + createErrorResponse(403, "Forbidden"), + ); await expect( executeApiRequest({ ...baseOptions, operation: "list" }), @@ -291,7 +340,7 @@ describe("ApiClient", () => { }); it("should handle error response with no message", async () => { - (mockPost as any).mockResolvedValue(createErrorResponse(500, "")); + (mockPost as jest.Mock).mockResolvedValue(createErrorResponse(500, "")); await expect( executeApiRequest({ ...baseOptions, operation: "list" }), @@ -301,7 +350,7 @@ describe("ApiClient", () => { describe("headers", () => { it("should include APIKey, Content-Type, and Accept headers", async () => { - (mockPost as any).mockResolvedValue(createSuccessResponse({})); + (mockPost as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, @@ -309,7 +358,8 @@ describe("ApiClient", () => { data: { name: "Test" }, }); - const callArgs = (mockPost as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockPost as jest.Mock).mock.calls[0][0]; expect(callArgs.headers["APIKey"]).toBe("test-api-key"); expect(callArgs.headers["Content-Type"]).toBe("application/json"); expect(callArgs.headers["Accept"]).toBe("application/json"); @@ -319,7 +369,7 @@ describe("ApiClient", () => { describe("default/unknown operation", () => { it("should handle unknown operation in buildRequestData (falls to default)", async () => { // The "delete" case hits the default branch in buildRequestData returning undefined - (mockDelete as any).mockResolvedValue(createSuccessResponse({})); + (mockDelete as jest.Mock).mockResolvedValue(createSuccessResponse({})); await executeApiRequest({ ...baseOptions, @@ -328,7 +378,8 @@ describe("ApiClient", () => { }); // Should not send data for delete - const callArgs = (mockDelete as any).mock.calls[0][0]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const callArgs: any = (mockDelete as jest.Mock).mock.calls[0][0]; expect(callArgs.data).toBeUndefined(); }); }); diff --git a/CLI/Tests/ConfigCommands.test.ts b/CLI/Tests/ConfigCommands.test.ts index eb1982e94f..b5f4a3c62c 100644 --- a/CLI/Tests/ConfigCommands.test.ts +++ b/CLI/Tests/ConfigCommands.test.ts @@ -5,8 +5,8 @@ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; -const CONFIG_DIR = path.join(os.homedir(), ".oneuptime"); -const CONFIG_FILE = path.join(CONFIG_DIR, "config.json"); +const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime"); +const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json"); describe("ConfigCommands", () => { let originalConfigContent: string | null = null; @@ -42,7 +42,7 @@ describe("ConfigCommands", () => { }); function createProgram(): Command { - const program = new Command(); + const program: Command = new Command(); program.exitOverride(); // Prevent commander from calling process.exit program.configureOutput({ writeOut: () => {}, @@ -54,7 +54,7 @@ describe("ConfigCommands", () => { describe("login command", () => { it("should create a context and set it as current", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", @@ -63,7 +63,8 @@ describe("ConfigCommands", () => { "https://example.com", ]); - const ctx = ConfigManager.getCurrentContext(); + const ctx: ReturnType = + ConfigManager.getCurrentContext(); expect(ctx).not.toBeNull(); expect(ctx!.name).toBe("default"); expect(ctx!.apiUrl).toBe("https://example.com"); @@ -71,7 +72,7 @@ describe("ConfigCommands", () => { }); it("should use custom context name", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", @@ -82,19 +83,20 @@ describe("ConfigCommands", () => { "production", ]); - const ctx = ConfigManager.getCurrentContext(); + const ctx: ReturnType = + ConfigManager.getCurrentContext(); expect(ctx!.name).toBe("production"); }); it("should handle login errors gracefully", async () => { // Mock addContext to throw - const addCtxSpy = jest + const addCtxSpy: jest.SpyInstance = jest .spyOn(ConfigManager, "addContext") .mockImplementation(() => { throw new Error("Permission denied"); }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", @@ -108,7 +110,7 @@ describe("ConfigCommands", () => { }); it("should strip trailing slashes from URL", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", @@ -117,14 +119,15 @@ describe("ConfigCommands", () => { "https://example.com///", ]); - const ctx = ConfigManager.getCurrentContext(); + const ctx: ReturnType = + ConfigManager.getCurrentContext(); expect(ctx!.apiUrl).toBe("https://example.com"); }); }); describe("context list command", () => { it("should show message when no contexts exist", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "list"]); expect(consoleLogSpy).toHaveBeenCalled(); }); @@ -141,7 +144,7 @@ describe("ConfigCommands", () => { apiKey: "k2", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "list"]); expect(consoleLogSpy).toHaveBeenCalled(); }); @@ -160,15 +163,16 @@ describe("ConfigCommands", () => { apiKey: "k2", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "use", "b"]); - const current = ConfigManager.getCurrentContext(); + const current: ReturnType = + ConfigManager.getCurrentContext(); expect(current!.name).toBe("b"); }); it("should handle non-existent context", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "use", "nope"]); expect(exitSpy).toHaveBeenCalledWith(1); @@ -183,7 +187,7 @@ describe("ConfigCommands", () => { apiKey: "abcdefghijklm", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "current"]); // Check that masked key is shown @@ -196,7 +200,7 @@ describe("ConfigCommands", () => { }); it("should show message when no current context", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "current"]); expect(consoleLogSpy).toHaveBeenCalled(); }); @@ -208,7 +212,7 @@ describe("ConfigCommands", () => { apiKey: "abc", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "context", "current"]); expect(consoleLogSpy).toHaveBeenCalledWith("API Key: ****"); @@ -223,7 +227,7 @@ describe("ConfigCommands", () => { apiKey: "k1", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", @@ -232,12 +236,13 @@ describe("ConfigCommands", () => { "todelete", ]); - const contexts = ConfigManager.listContexts(); + const contexts: ReturnType = + ConfigManager.listContexts(); expect(contexts).toHaveLength(0); }); it("should handle deletion of non-existent context", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", diff --git a/CLI/Tests/ConfigManager.test.ts b/CLI/Tests/ConfigManager.test.ts index 4820196fea..49ed6ee16b 100644 --- a/CLI/Tests/ConfigManager.test.ts +++ b/CLI/Tests/ConfigManager.test.ts @@ -2,10 +2,10 @@ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import * as ConfigManager from "../Core/ConfigManager"; -import { CLIContext, CLIConfig, ResolvedCredentials } from "../Types/CLITypes"; +import { CLIConfig, ResolvedCredentials } from "../Types/CLITypes"; -const CONFIG_DIR = path.join(os.homedir(), ".oneuptime"); -const CONFIG_FILE = path.join(CONFIG_DIR, "config.json"); +const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime"); +const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json"); describe("ConfigManager", () => { let originalConfigContent: string | null = null; @@ -39,7 +39,7 @@ describe("ConfigManager", () => { describe("load", () => { it("should return default config when no config file exists", () => { - const config = ConfigManager.load(); + const config: CLIConfig = ConfigManager.load(); expect(config.currentContext).toBe(""); expect(config.contexts).toEqual({}); expect(config.defaults.output).toBe("table"); @@ -61,7 +61,7 @@ describe("ConfigManager", () => { mode: 0o600, }); - const config = ConfigManager.load(); + const config: CLIConfig = ConfigManager.load(); expect(config.currentContext).toBe("test"); expect(config.contexts["test"]?.apiKey).toBe("key123"); }); @@ -72,7 +72,7 @@ describe("ConfigManager", () => { } fs.writeFileSync(CONFIG_FILE, "not valid json {{{", { mode: 0o600 }); - const config = ConfigManager.load(); + const config: CLIConfig = ConfigManager.load(); expect(config.currentContext).toBe(""); expect(config.contexts).toEqual({}); }); @@ -81,9 +81,14 @@ describe("ConfigManager", () => { describe("save", () => { it("should create config directory if it does not exist", () => { // Remove the dir if it exists (we'll restore after) - const tmpDir = path.join(os.tmpdir(), ".oneuptime-test-" + Date.now()); - // We can't easily test this with the real path, but we verify save works - // when the dir already exists (which it does after beforeAll). + const tmpDir: string = path.join( + os.tmpdir(), + ".oneuptime-test-" + Date.now(), + ); + /* + * We can't easily test this with the real path, but we verify save works + * when the dir already exists (which it does after beforeAll). + */ const config: CLIConfig = { currentContext: "", contexts: {}, @@ -103,8 +108,8 @@ describe("ConfigManager", () => { defaults: { output: "table", limit: 10 }, }; ConfigManager.save(config); - const content = fs.readFileSync(CONFIG_FILE, "utf-8"); - const parsed = JSON.parse(content); + const content: string = fs.readFileSync(CONFIG_FILE, "utf-8"); + const parsed: CLIConfig = JSON.parse(content); expect(parsed.currentContext).toBe("x"); }); }); @@ -131,7 +136,8 @@ describe("ConfigManager", () => { apiUrl: "https://prod.com", apiKey: "k1", }); - const ctx = ConfigManager.getCurrentContext(); + const ctx: ReturnType = + ConfigManager.getCurrentContext(); expect(ctx).not.toBeNull(); expect(ctx!.name).toBe("prod"); }); @@ -145,7 +151,8 @@ describe("ConfigManager", () => { apiKey: "sk-prod-123", }); - const current = ConfigManager.getCurrentContext(); + const current: ReturnType = + ConfigManager.getCurrentContext(); expect(current).not.toBeNull(); expect(current!.name).toBe("prod"); }); @@ -162,7 +169,8 @@ describe("ConfigManager", () => { apiKey: "key2", }); - const current = ConfigManager.getCurrentContext(); + const current: ReturnType = + ConfigManager.getCurrentContext(); expect(current!.name).toBe("prod"); // First one remains current }); @@ -178,7 +186,8 @@ describe("ConfigManager", () => { apiKey: "key2", }); - const contexts = ConfigManager.listContexts(); + const contexts: ReturnType = + ConfigManager.listContexts(); expect(contexts).toHaveLength(2); }); }); @@ -197,14 +206,15 @@ describe("ConfigManager", () => { }); ConfigManager.setCurrentContext("b"); - const current = ConfigManager.getCurrentContext(); + const current: ReturnType = + ConfigManager.getCurrentContext(); expect(current!.name).toBe("b"); }); it("should throw for non-existent context", () => { - expect(() => ConfigManager.setCurrentContext("nonexistent")).toThrow( - 'Context "nonexistent" does not exist', - ); + expect(() => { + return ConfigManager.setCurrentContext("nonexistent"); + }).toThrow('Context "nonexistent" does not exist'); }); }); @@ -217,14 +227,15 @@ describe("ConfigManager", () => { }); ConfigManager.removeContext("test"); - const contexts = ConfigManager.listContexts(); + const contexts: ReturnType = + ConfigManager.listContexts(); expect(contexts).toHaveLength(0); }); it("should throw for non-existent context", () => { - expect(() => ConfigManager.removeContext("nonexistent")).toThrow( - 'Context "nonexistent" does not exist', - ); + expect(() => { + return ConfigManager.removeContext("nonexistent"); + }).toThrow('Context "nonexistent" does not exist'); }); it("should update current context when removing the current one", () => { @@ -241,7 +252,8 @@ describe("ConfigManager", () => { ConfigManager.setCurrentContext("a"); ConfigManager.removeContext("a"); - const current = ConfigManager.getCurrentContext(); + const current: ReturnType = + ConfigManager.getCurrentContext(); expect(current).not.toBeNull(); expect(current!.name).toBe("b"); }); @@ -255,7 +267,7 @@ describe("ConfigManager", () => { ConfigManager.removeContext("only"); expect(ConfigManager.getCurrentContext()).toBeNull(); - const config = ConfigManager.load(); + const config: CLIConfig = ConfigManager.load(); expect(config.currentContext).toBe(""); }); @@ -273,7 +285,8 @@ describe("ConfigManager", () => { ConfigManager.setCurrentContext("a"); ConfigManager.removeContext("b"); - const current = ConfigManager.getCurrentContext(); + const current: ReturnType = + ConfigManager.getCurrentContext(); expect(current!.name).toBe("a"); }); }); @@ -296,9 +309,22 @@ describe("ConfigManager", () => { }); ConfigManager.setCurrentContext("b"); - const contexts = ConfigManager.listContexts(); - const a = contexts.find((c) => c.name === "a"); - const b = contexts.find((c) => c.name === "b"); + const contexts: ReturnType = + ConfigManager.listContexts(); + const a: + | ReturnType[number] + | undefined = contexts.find( + (c: ReturnType[number]) => { + return c.name === "a"; + }, + ); + const b: + | ReturnType[number] + | undefined = contexts.find( + (c: ReturnType[number]) => { + return c.name === "b"; + }, + ); expect(a!.isCurrent).toBe(false); expect(b!.isCurrent).toBe(true); }); @@ -340,9 +366,9 @@ describe("ConfigManager", () => { }); it("should throw when --context flag references non-existent context", () => { - expect(() => - ConfigManager.getResolvedCredentials({ context: "nope" }), - ).toThrow('Context "nope" does not exist'); + expect(() => { + return ConfigManager.getResolvedCredentials({ context: "nope" }); + }).toThrow('Context "nope" does not exist'); }); it("should resolve from current context in config", () => { @@ -390,17 +416,19 @@ describe("ConfigManager", () => { const creds: ResolvedCredentials = ConfigManager.getResolvedCredentials( {}, ); - // env vars take priority: both are set so goes through priority 2 - // Actually, only ONEUPTIME_API_KEY is set, not ONEUPTIME_URL - // So it falls through to priority 4 (current context) + /* + * env vars take priority: both are set so goes through priority 2 + * Actually, only ONEUPTIME_API_KEY is set, not ONEUPTIME_URL + * So it falls through to priority 4 (current context) + */ expect(creds.apiKey).toBe("ctx-key"); expect(creds.apiUrl).toBe("https://ctx.com"); }); it("should throw when no credentials available at all", () => { - expect(() => ConfigManager.getResolvedCredentials({})).toThrow( - "No credentials found", - ); + expect(() => { + return ConfigManager.getResolvedCredentials({}); + }).toThrow("No credentials found"); }); it("should prefer CLI flags over env vars", () => { diff --git a/CLI/Tests/ErrorHandler.test.ts b/CLI/Tests/ErrorHandler.test.ts index e074216b62..a5bbb74296 100644 --- a/CLI/Tests/ErrorHandler.test.ts +++ b/CLI/Tests/ErrorHandler.test.ts @@ -9,9 +9,11 @@ describe("ErrorHandler", () => { exitSpy = jest.spyOn(process, "exit").mockImplementation((() => { // no-op }) as any); - printErrorSpy = jest.spyOn(OutputFormatter, "printError").mockImplementation(() => { - // no-op - }); + printErrorSpy = jest + .spyOn(OutputFormatter, "printError") + .mockImplementation(() => { + // no-op + }); }); afterEach(() => { @@ -44,9 +46,7 @@ describe("ErrorHandler", () => { it("should exit with NotFound for 404 errors", () => { handleError(new Error("HTTP 404 response")); - expect(printErrorSpy).toHaveBeenCalledWith( - "Not found: HTTP 404 response", - ); + expect(printErrorSpy).toHaveBeenCalledWith("Not found: HTTP 404 response"); expect(exitSpy).toHaveBeenCalledWith(ExitCode.NotFound); }); @@ -65,9 +65,7 @@ describe("ErrorHandler", () => { it("should exit with GeneralError for generic Error objects", () => { handleError(new Error("Something went wrong")); - expect(printErrorSpy).toHaveBeenCalledWith( - "Error: Something went wrong", - ); + expect(printErrorSpy).toHaveBeenCalledWith("Error: Something went wrong"); expect(exitSpy).toHaveBeenCalledWith(ExitCode.GeneralError); }); diff --git a/CLI/Tests/Index.test.ts b/CLI/Tests/Index.test.ts index 24e7b578e3..1bb542c855 100644 --- a/CLI/Tests/Index.test.ts +++ b/CLI/Tests/Index.test.ts @@ -1,11 +1,11 @@ -import { Command } from "commander"; +import { Command, Option } from "commander"; import { registerConfigCommands } from "../Commands/ConfigCommands"; import { registerResourceCommands } from "../Commands/ResourceCommands"; import { registerUtilityCommands } from "../Commands/UtilityCommands"; describe("Index (CLI entry point)", () => { it("should create a program with all command groups registered", () => { - const program = new Command(); + const program: Command = new Command(); program .name("oneuptime") .description( @@ -23,7 +23,9 @@ describe("Index (CLI entry point)", () => { registerResourceCommands(program); // Verify all expected commands are registered - const commandNames = program.commands.map((c) => c.name()); + const commandNames: string[] = program.commands.map((c: Command) => { + return c.name(); + }); expect(commandNames).toContain("login"); expect(commandNames).toContain("context"); expect(commandNames).toContain("version"); @@ -35,14 +37,14 @@ describe("Index (CLI entry point)", () => { }); it("should set correct program name and description", () => { - const program = new Command(); + const program: Command = new Command(); program.name("oneuptime").description("OneUptime CLI"); expect(program.name()).toBe("oneuptime"); }); it("should define global options", () => { - const program = new Command(); + const program: Command = new Command(); program .option("--api-key ", "API key") .option("--url ", "URL") @@ -51,8 +53,10 @@ describe("Index (CLI entry point)", () => { .option("--no-color", "Disable color"); // Parse with just the program name - verify options are registered - const options = program.options; - const optionNames = options.map((o) => o.long || o.short); + const options: readonly Option[] = program.options; + const optionNames: (string | undefined)[] = options.map((o: Option) => { + return o.long || o.short; + }); expect(optionNames).toContain("--api-key"); expect(optionNames).toContain("--url"); expect(optionNames).toContain("--context"); diff --git a/CLI/Tests/OutputFormatter.test.ts b/CLI/Tests/OutputFormatter.test.ts index 3a1ef43388..44907c878a 100644 --- a/CLI/Tests/OutputFormatter.test.ts +++ b/CLI/Tests/OutputFormatter.test.ts @@ -5,6 +5,7 @@ import { printWarning, printInfo, } from "../Core/OutputFormatter"; +import { JSONObject } from "Common/Types/JSON"; describe("OutputFormatter", () => { let consoleLogSpy: jest.SpyInstance; @@ -32,48 +33,48 @@ describe("OutputFormatter", () => { describe("formatOutput with JSON format", () => { it("should format single object as JSON", () => { - const data = { id: "123", name: "Test" }; - const result = formatOutput(data, "json"); + const data: Record = { id: "123", name: "Test" }; + const result: string = formatOutput(data, "json"); expect(JSON.parse(result)).toEqual(data); }); it("should format array as JSON", () => { - const data = [ + const data: Record[] = [ { id: "1", name: "A" }, { id: "2", name: "B" }, ]; - const result = formatOutput(data, "json"); + const result: string = formatOutput(data, "json"); expect(JSON.parse(result)).toEqual(data); }); it("should format null as JSON", () => { - const result = formatOutput(null, "json"); + const result: string = formatOutput(null, "json"); expect(result).toBe("null"); }); it("should format number as JSON", () => { - const result = formatOutput(42, "json"); + const result: string = formatOutput(42, "json"); expect(result).toBe("42"); }); it("should format string as JSON", () => { - const result = formatOutput("hello", "json"); + const result: string = formatOutput("hello", "json"); expect(result).toBe('"hello"'); }); it("should format boolean as JSON", () => { - const result = formatOutput(true, "json"); + const result: string = formatOutput(true, "json"); expect(result).toBe("true"); }); }); describe("formatOutput with table format", () => { it("should format array as table", () => { - const data = [ + const data: Record[] = [ { _id: "1", name: "A" }, { _id: "2", name: "B" }, ]; - const result = formatOutput(data, "table"); + const result: string = formatOutput(data, "table"); expect(result).toContain("1"); expect(result).toContain("A"); expect(result).toContain("2"); @@ -81,76 +82,76 @@ describe("OutputFormatter", () => { }); it("should handle empty array", () => { - const result = formatOutput([], "table"); + const result: string = formatOutput([], "table"); expect(result).toBe("No results found."); }); it("should handle single object as key-value table", () => { - const data = { name: "Test", status: "Active" }; - const result = formatOutput(data, "table"); + const data: Record = { name: "Test", status: "Active" }; + const result: string = formatOutput(data, "table"); expect(result).toContain("Test"); expect(result).toContain("Active"); }); it("should return 'No data returned.' for null in table mode", () => { - const result = formatOutput(null, "table"); + const result: string = formatOutput(null, "table"); expect(result).toBe("No data returned."); }); it("should return 'No data returned.' for undefined in table mode", () => { - const result = formatOutput(undefined as any, "table"); + const result: string = formatOutput(undefined as any, "table"); expect(result).toBe("No data returned."); }); it("should return 'No data returned.' for empty string in table mode", () => { - const result = formatOutput("" as any, "table"); + const result: string = formatOutput("" as any, "table"); expect(result).toBe("No data returned."); }); it("should fallback to JSON for array of non-objects", () => { - const data = ["a", "b", "c"]; - const result = formatOutput(data, "table"); + const data: string[] = ["a", "b", "c"]; + const result: string = formatOutput(data, "table"); // First item is not an object, so should fallback to JSON expect(result).toContain('"a"'); }); it("should truncate long string values", () => { - const longValue = "x".repeat(100); - const data = [{ _id: "1", field: longValue }]; - const result = formatOutput(data, "table"); + const longValue: string = "x".repeat(100); + const data: Record[] = [{ _id: "1", field: longValue }]; + const result: string = formatOutput(data, "table"); expect(result).toContain("..."); }); it("should truncate long object values", () => { - const bigObj = { a: "x".repeat(80) }; - const data = [{ _id: "1", nested: bigObj }]; - const result = formatOutput(data, "table"); + const bigObj: Record = { a: "x".repeat(80) }; + const data: JSONObject[] = [{ _id: "1", nested: bigObj }]; + const result: string = formatOutput(data, "table"); expect(result).toContain("..."); }); it("should show short object values without truncation", () => { - const smallObj = { a: 1 }; - const data = [{ _id: "1", nested: smallObj }]; - const result = formatOutput(data, "table"); + const smallObj: Record = { a: 1 }; + const data: JSONObject[] = [{ _id: "1", nested: smallObj }]; + const result: string = formatOutput(data, "table"); expect(result).toContain('{"a":1}'); }); it("should render null values as empty in table", () => { - const data = [{ _id: "1", value: null }]; - const result = formatOutput(data, "table"); + const data: JSONObject[] = [{ _id: "1", value: null }]; + const result: string = formatOutput(data, "table"); expect(result).toContain("1"); }); it("should render undefined values as empty in table", () => { - const data = [{ _id: "1", value: undefined }]; - const result = formatOutput(data, "table"); + const data: JSONObject[] = [{ _id: "1", value: undefined }]; + const result: string = formatOutput(data, "table"); expect(result).toContain("1"); }); }); describe("formatOutput with wide format", () => { it("should show all columns in wide mode", () => { - const data = [ + const data: Record[] = [ { _id: "1", name: "A", @@ -163,12 +164,12 @@ describe("OutputFormatter", () => { col7: "t", }, ]; - const result = formatOutput(data, "wide"); + const result: string = formatOutput(data, "wide"); expect(result).toContain("col7"); }); it("should limit columns in non-wide table mode", () => { - const data = [ + const data: Record[] = [ { _id: "1", name: "A", @@ -181,13 +182,13 @@ describe("OutputFormatter", () => { col7: "t", }, ]; - const result = formatOutput(data, "table"); + const result: string = formatOutput(data, "table"); // Table mode should limit to 6 columns, so col7 should not appear expect(result).not.toContain("col7"); }); it("should prioritize common columns in non-wide mode", () => { - const data = [ + const data: Record[] = [ { extra1: "a", extra2: "b", @@ -203,7 +204,7 @@ describe("OutputFormatter", () => { updatedAt: "2024-01-02", }, ]; - const result = formatOutput(data, "table"); + const result: string = formatOutput(data, "table"); // Priority columns should appear expect(result).toContain("_id"); expect(result).toContain("name"); @@ -212,16 +213,18 @@ describe("OutputFormatter", () => { describe("format auto-detection", () => { it("should default to JSON when not a TTY", () => { - const originalIsTTY = process.stdout.isTTY; + const originalIsTTY: boolean | undefined = process.stdout.isTTY; Object.defineProperty(process.stdout, "isTTY", { value: false, writable: true, configurable: true, }); - const data = { id: "1" }; - const result = formatOutput(data); - expect(() => JSON.parse(result)).not.toThrow(); + const data: Record = { id: "1" }; + const result: string = formatOutput(data); + expect(() => { + return JSON.parse(result); + }).not.toThrow(); Object.defineProperty(process.stdout, "isTTY", { value: originalIsTTY, @@ -231,17 +234,17 @@ describe("OutputFormatter", () => { }); it("should default to table when TTY", () => { - const originalIsTTY = process.stdout.isTTY; + const originalIsTTY: boolean | undefined = process.stdout.isTTY; Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true, configurable: true, }); - const data = [{ _id: "1", name: "Test" }]; - const result = formatOutput(data); + const data: Record[] = [{ _id: "1", name: "Test" }]; + const result: string = formatOutput(data); // Table format contains box-drawing characters - expect(result).toContain("─"); + expect(result).toContain("\u2500"); Object.defineProperty(process.stdout, "isTTY", { value: originalIsTTY, @@ -251,17 +254,17 @@ describe("OutputFormatter", () => { }); it("should handle unknown format string and default to table via TTY check", () => { - const data = [{ _id: "1" }]; + const data: Record[] = [{ _id: "1" }]; // "unknown" is not json/table/wide, so cliFormat falls through and TTY detection occurs - const originalIsTTY = process.stdout.isTTY; + const originalIsTTY: boolean | undefined = process.stdout.isTTY; Object.defineProperty(process.stdout, "isTTY", { value: true, writable: true, configurable: true, }); - const result = formatOutput(data, "unknown"); - expect(result).toContain("─"); + const result: string = formatOutput(data, "unknown"); + expect(result).toContain("\u2500"); Object.defineProperty(process.stdout, "isTTY", { value: originalIsTTY, @@ -274,23 +277,26 @@ describe("OutputFormatter", () => { describe("color handling", () => { it("should respect NO_COLOR env variable in table rendering", () => { process.env["NO_COLOR"] = "1"; - const data = [{ _id: "1", name: "A" }]; - const result = formatOutput(data, "table"); + const data: Record[] = [{ _id: "1", name: "A" }]; + const result: string = formatOutput(data, "table"); // Should not contain ANSI color codes + // eslint-disable-next-line no-control-regex expect(result).not.toMatch(/\x1b\[/); }); it("should respect --no-color argv flag in table rendering", () => { process.argv.push("--no-color"); - const data = [{ _id: "1", name: "A" }]; - const result = formatOutput(data, "table"); + const data: Record[] = [{ _id: "1", name: "A" }]; + const result: string = formatOutput(data, "table"); + // eslint-disable-next-line no-control-regex expect(result).not.toMatch(/\x1b\[/); }); it("should render single object without color when NO_COLOR set", () => { process.env["NO_COLOR"] = "1"; - const data = { name: "Test" }; - const result = formatOutput(data, "table"); + const data: Record = { name: "Test" }; + const result: string = formatOutput(data, "table"); + // eslint-disable-next-line no-control-regex expect(result).not.toMatch(/\x1b\[/); expect(result).toContain("name"); }); @@ -300,7 +306,9 @@ describe("OutputFormatter", () => { it("should log success message with color", () => { delete process.env["NO_COLOR"]; // Remove --no-color from argv if present - process.argv = process.argv.filter((a) => a !== "--no-color"); + process.argv = process.argv.filter((a: string) => { + return a !== "--no-color"; + }); printSuccess("OK"); expect(consoleLogSpy).toHaveBeenCalled(); }); @@ -315,7 +323,9 @@ describe("OutputFormatter", () => { describe("printError", () => { it("should log error message with color", () => { delete process.env["NO_COLOR"]; - process.argv = process.argv.filter((a) => a !== "--no-color"); + process.argv = process.argv.filter((a: string) => { + return a !== "--no-color"; + }); printError("fail"); expect(consoleErrorSpy).toHaveBeenCalled(); }); @@ -330,7 +340,9 @@ describe("OutputFormatter", () => { describe("printWarning", () => { it("should log warning message with color", () => { delete process.env["NO_COLOR"]; - process.argv = process.argv.filter((a) => a !== "--no-color"); + process.argv = process.argv.filter((a: string) => { + return a !== "--no-color"; + }); printWarning("warn"); expect(consoleErrorSpy).toHaveBeenCalled(); }); @@ -345,7 +357,9 @@ describe("OutputFormatter", () => { describe("printInfo", () => { it("should log info message with color", () => { delete process.env["NO_COLOR"]; - process.argv = process.argv.filter((a) => a !== "--no-color"); + process.argv = process.argv.filter((a: string) => { + return a !== "--no-color"; + }); printInfo("info"); expect(consoleLogSpy).toHaveBeenCalled(); }); diff --git a/CLI/Tests/ResourceCommands.test.ts b/CLI/Tests/ResourceCommands.test.ts index a86df132b3..c80d8a98f8 100644 --- a/CLI/Tests/ResourceCommands.test.ts +++ b/CLI/Tests/ResourceCommands.test.ts @@ -6,11 +6,15 @@ import * as path from "path"; import * as os from "os"; // Mock the ApiClient module before it's imported by ResourceCommands -const mockExecuteApiRequest = jest.fn(); -jest.mock("../Core/ApiClient", () => ({ - ...jest.requireActual("../Core/ApiClient"), - executeApiRequest: (...args) => mockExecuteApiRequest(...args), -})); +const mockExecuteApiRequest: jest.Mock = jest.fn(); +jest.mock("../Core/ApiClient", () => { + return { + ...jest.requireActual("../Core/ApiClient"), + executeApiRequest: (...args: unknown[]): unknown => { + return mockExecuteApiRequest(...args); + }, + }; +}); // Import after mock setup import { @@ -18,8 +22,8 @@ import { registerResourceCommands, } from "../Commands/ResourceCommands"; -const CONFIG_DIR = path.join(os.homedir(), ".oneuptime"); -const CONFIG_FILE = path.join(CONFIG_DIR, "config.json"); +const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime"); +const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json"); describe("ResourceCommands", () => { let originalConfigContent: string | null = null; @@ -68,20 +72,32 @@ describe("ResourceCommands", () => { }); it("should discover the Incident resource", () => { - const incident = resources.find((r) => r.singularName === "Incident"); + const incident: ResourceInfo | undefined = resources.find( + (r: ResourceInfo) => { + return r.singularName === "Incident"; + }, + ); expect(incident).toBeDefined(); expect(incident!.modelType).toBe("database"); expect(incident!.apiPath).toBe("/incident"); }); it("should discover the Monitor resource", () => { - const monitor = resources.find((r) => r.singularName === "Monitor"); + const monitor: ResourceInfo | undefined = resources.find( + (r: ResourceInfo) => { + return r.singularName === "Monitor"; + }, + ); expect(monitor).toBeDefined(); expect(monitor!.modelType).toBe("database"); }); it("should discover the Alert resource", () => { - const alert = resources.find((r) => r.singularName === "Alert"); + const alert: ResourceInfo | undefined = resources.find( + (r: ResourceInfo) => { + return r.singularName === "Alert"; + }, + ); expect(alert).toBeDefined(); }); @@ -107,28 +123,34 @@ describe("ResourceCommands", () => { describe("registerResourceCommands", () => { it("should register commands for all discovered resources", () => { - const program = new Command(); + const program: Command = new Command(); program.exitOverride(); registerResourceCommands(program); - const resources = discoverResources(); + const resources: ResourceInfo[] = discoverResources(); for (const resource of resources) { - const cmd = program.commands.find((c) => c.name() === resource.name); + const cmd: Command | undefined = program.commands.find((c: Command) => { + return c.name() === resource.name; + }); expect(cmd).toBeDefined(); } }); it("should register list, get, create, update, delete, count subcommands for database resources", () => { - const program = new Command(); + const program: Command = new Command(); program.exitOverride(); registerResourceCommands(program); - const incidentCmd = program.commands.find( - (c) => c.name() === "incident", + const incidentCmd: Command | undefined = program.commands.find( + (c: Command) => { + return c.name() === "incident"; + }, ); expect(incidentCmd).toBeDefined(); - const subcommands = incidentCmd!.commands.map((c) => c.name()); + const subcommands: string[] = incidentCmd!.commands.map((c: Command) => { + return c.name(); + }); expect(subcommands).toContain("list"); expect(subcommands).toContain("get"); expect(subcommands).toContain("create"); @@ -140,7 +162,7 @@ describe("ResourceCommands", () => { describe("resource command actions", () => { function createProgramWithResources(): Command { - const program = new Command(); + const program: Command = new Command(); program.exitOverride(); program.configureOutput({ writeOut: () => {}, @@ -165,16 +187,18 @@ describe("ResourceCommands", () => { describe("list subcommand", () => { it("should call API with list operation", async () => { - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "list"]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); expect(mockExecuteApiRequest.mock.calls[0][0].operation).toBe("list"); - expect(mockExecuteApiRequest.mock.calls[0][0].apiPath).toBe("/incident"); + expect(mockExecuteApiRequest.mock.calls[0][0].apiPath).toBe( + "/incident", + ); }); it("should pass query, limit, skip, sort options", async () => { - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -191,7 +215,8 @@ describe("ResourceCommands", () => { ]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); - const opts = mockExecuteApiRequest.mock.calls[0][0]; + const opts: Record = + mockExecuteApiRequest.mock.calls[0][0]; expect(opts.query).toEqual({ status: "active" }); expect(opts.limit).toBe(20); expect(opts.skip).toBe(5); @@ -203,7 +228,7 @@ describe("ResourceCommands", () => { data: [{ _id: "1", name: "Test" }], }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -213,13 +238,14 @@ describe("ResourceCommands", () => { "json", ]); + // eslint-disable-next-line no-console expect(console.log).toHaveBeenCalled(); }); it("should handle response that is already an array", async () => { mockExecuteApiRequest.mockResolvedValue([{ _id: "1" }]); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -229,6 +255,7 @@ describe("ResourceCommands", () => { "json", ]); + // eslint-disable-next-line no-console expect(console.log).toHaveBeenCalled(); }); @@ -237,7 +264,7 @@ describe("ResourceCommands", () => { new Error("API error (500): Server Error"), ); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "list"]); expect(process.exit).toHaveBeenCalled(); @@ -251,7 +278,7 @@ describe("ResourceCommands", () => { name: "Test", }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -261,7 +288,8 @@ describe("ResourceCommands", () => { ]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); - const opts = mockExecuteApiRequest.mock.calls[0][0]; + const opts: Record = + mockExecuteApiRequest.mock.calls[0][0]; expect(opts.operation).toBe("read"); expect(opts.id).toBe("abc-123"); }); @@ -269,7 +297,7 @@ describe("ResourceCommands", () => { it("should support output format flag", async () => { mockExecuteApiRequest.mockResolvedValue({ _id: "abc-123" }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -280,13 +308,14 @@ describe("ResourceCommands", () => { "json", ]); + // eslint-disable-next-line no-console expect(console.log).toHaveBeenCalled(); }); it("should handle get errors", async () => { mockExecuteApiRequest.mockRejectedValue(new Error("not found 404")); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -303,7 +332,7 @@ describe("ResourceCommands", () => { it("should call API with create operation and data", async () => { mockExecuteApiRequest.mockResolvedValue({ _id: "new-123" }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -314,7 +343,8 @@ describe("ResourceCommands", () => { ]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); - const opts = mockExecuteApiRequest.mock.calls[0][0]; + const opts: Record = + mockExecuteApiRequest.mock.calls[0][0]; expect(opts.operation).toBe("create"); expect(opts.data).toEqual({ name: "New Incident" }); }); @@ -322,14 +352,14 @@ describe("ResourceCommands", () => { it("should support reading data from a file", async () => { mockExecuteApiRequest.mockResolvedValue({ _id: "new-123" }); - const tmpFile = path.join( + const tmpFile: string = path.join( os.tmpdir(), "cli-test-" + Date.now() + ".json", ); fs.writeFileSync(tmpFile, '{"name":"From File"}'); try { - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -349,14 +379,14 @@ describe("ResourceCommands", () => { }); it("should error when neither --data nor --file is provided", async () => { - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "create"]); expect(process.exit).toHaveBeenCalled(); }); it("should error on invalid JSON in --data", async () => { - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -374,7 +404,7 @@ describe("ResourceCommands", () => { it("should call API with update operation, id, and data", async () => { mockExecuteApiRequest.mockResolvedValue({ _id: "abc-123" }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -386,7 +416,8 @@ describe("ResourceCommands", () => { ]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); - const opts = mockExecuteApiRequest.mock.calls[0][0]; + const opts: Record = + mockExecuteApiRequest.mock.calls[0][0]; expect(opts.operation).toBe("update"); expect(opts.id).toBe("abc-123"); expect(opts.data).toEqual({ name: "Updated" }); @@ -395,7 +426,7 @@ describe("ResourceCommands", () => { it("should handle update errors", async () => { mockExecuteApiRequest.mockRejectedValue(new Error("API error")); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -414,7 +445,7 @@ describe("ResourceCommands", () => { it("should call API with delete operation and id", async () => { mockExecuteApiRequest.mockResolvedValue({}); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -424,7 +455,8 @@ describe("ResourceCommands", () => { ]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); - const opts = mockExecuteApiRequest.mock.calls[0][0]; + const opts: Record = + mockExecuteApiRequest.mock.calls[0][0]; expect(opts.operation).toBe("delete"); expect(opts.id).toBe("abc-123"); }); @@ -432,7 +464,7 @@ describe("ResourceCommands", () => { it("should handle API errors", async () => { mockExecuteApiRequest.mockRejectedValue(new Error("not found 404")); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -449,18 +481,19 @@ describe("ResourceCommands", () => { it("should call API with count operation", async () => { mockExecuteApiRequest.mockResolvedValue({ count: 42 }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "count"]); expect(mockExecuteApiRequest).toHaveBeenCalledTimes(1); expect(mockExecuteApiRequest.mock.calls[0][0].operation).toBe("count"); + // eslint-disable-next-line no-console expect(console.log).toHaveBeenCalledWith(42); }); it("should pass query filter", async () => { mockExecuteApiRequest.mockResolvedValue({ count: 5 }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", @@ -478,25 +511,27 @@ describe("ResourceCommands", () => { it("should handle response without count field", async () => { mockExecuteApiRequest.mockResolvedValue(99); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "count"]); + // eslint-disable-next-line no-console expect(console.log).toHaveBeenCalledWith(99); }); it("should handle non-object response in count", async () => { mockExecuteApiRequest.mockResolvedValue("some-string"); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "count"]); + // eslint-disable-next-line no-console expect(console.log).toHaveBeenCalledWith("some-string"); }); it("should handle count errors", async () => { mockExecuteApiRequest.mockRejectedValue(new Error("API error")); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync(["node", "test", "incident", "count"]); expect(process.exit).toHaveBeenCalled(); @@ -508,7 +543,7 @@ describe("ResourceCommands", () => { ConfigManager.removeContext("test"); mockExecuteApiRequest.mockResolvedValue({ data: [] }); - const program = createProgramWithResources(); + const program: Command = createProgramWithResources(); await program.parseAsync([ "node", "test", diff --git a/CLI/Tests/SelectFieldGenerator.test.ts b/CLI/Tests/SelectFieldGenerator.test.ts index 795809ff6e..a6cbd5df62 100644 --- a/CLI/Tests/SelectFieldGenerator.test.ts +++ b/CLI/Tests/SelectFieldGenerator.test.ts @@ -1,22 +1,29 @@ import { generateAllFieldsSelect } from "../Utils/SelectFieldGenerator"; +import { JSONObject } from "Common/Types/JSON"; describe("SelectFieldGenerator", () => { describe("generateAllFieldsSelect", () => { describe("database models", () => { it("should return fields for a known database model (Incident)", () => { - const select = generateAllFieldsSelect("Incident", "database"); + const select: JSONObject = generateAllFieldsSelect( + "Incident", + "database", + ); expect(Object.keys(select).length).toBeGreaterThan(0); // Should have some common fields expect(select).toHaveProperty("_id"); }); it("should return fields for Monitor model", () => { - const select = generateAllFieldsSelect("Monitor", "database"); + const select: JSONObject = generateAllFieldsSelect( + "Monitor", + "database", + ); expect(Object.keys(select).length).toBeGreaterThan(0); }); it("should return default select for unknown database model", () => { - const select = generateAllFieldsSelect( + const select: JSONObject = generateAllFieldsSelect( "NonExistentModel12345", "database", ); @@ -29,7 +36,10 @@ describe("SelectFieldGenerator", () => { it("should filter fields based on access control", () => { // Testing with a real model that has access control - const select = generateAllFieldsSelect("Incident", "database"); + const select: JSONObject = generateAllFieldsSelect( + "Incident", + "database", + ); // We just verify it returns something reasonable expect(typeof select).toBe("object"); expect(Object.keys(select).length).toBeGreaterThan(0); @@ -39,7 +49,10 @@ describe("SelectFieldGenerator", () => { describe("analytics models", () => { it("should return default select for known analytics model (LogItem)", () => { // The Log analytics model has tableName "LogItem" - const select = generateAllFieldsSelect("LogItem", "analytics"); + const select: JSONObject = generateAllFieldsSelect( + "LogItem", + "analytics", + ); expect(select).toEqual({ _id: true, createdAt: true, @@ -48,7 +61,7 @@ describe("SelectFieldGenerator", () => { }); it("should return default select for unknown analytics model", () => { - const select = generateAllFieldsSelect( + const select: JSONObject = generateAllFieldsSelect( "NonExistentAnalytics", "analytics", ); @@ -62,7 +75,7 @@ describe("SelectFieldGenerator", () => { describe("edge cases", () => { it("should return default select for unknown model type", () => { - const select = generateAllFieldsSelect( + const select: JSONObject = generateAllFieldsSelect( "Incident", "unknown" as any, ); @@ -74,7 +87,7 @@ describe("SelectFieldGenerator", () => { }); it("should return default select for empty tableName", () => { - const select = generateAllFieldsSelect("", "database"); + const select: JSONObject = generateAllFieldsSelect("", "database"); expect(select).toEqual({ _id: true, createdAt: true, @@ -83,14 +96,20 @@ describe("SelectFieldGenerator", () => { }); it("should handle outer exception and return default select", () => { - const DatabaseModels = require("Common/Models/DatabaseModels/Index").default; - const origFind = DatabaseModels.find; + /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ + const DatabaseModels: Record = + require("Common/Models/DatabaseModels/Index").default; + /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ + const origFind: unknown = DatabaseModels.find; try { - DatabaseModels.find = () => { + DatabaseModels.find = (): never => { throw new Error("Simulated error"); }; - const select = generateAllFieldsSelect("Incident", "database"); + const select: JSONObject = generateAllFieldsSelect( + "Incident", + "database", + ); expect(select).toEqual({ _id: true, createdAt: true, @@ -102,12 +121,22 @@ describe("SelectFieldGenerator", () => { }); it("should return default when getTableColumns returns empty", () => { - const tableColumnModule = require("Common/Types/Database/TableColumn"); - const origGetTableColumns = tableColumnModule.getTableColumns; + /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ + const tableColumnModule: Record< + string, + unknown + > = require("Common/Types/Database/TableColumn"); + /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ + const origGetTableColumns: unknown = tableColumnModule.getTableColumns; try { - tableColumnModule.getTableColumns = () => ({}); + tableColumnModule.getTableColumns = (): Record => { + return {}; + }; - const select = generateAllFieldsSelect("Incident", "database"); + const select: JSONObject = generateAllFieldsSelect( + "Incident", + "database", + ); expect(select).toEqual({ _id: true, createdAt: true, @@ -119,29 +148,51 @@ describe("SelectFieldGenerator", () => { }); it("should return default when all columns are filtered out", () => { - const tableColumnModule = require("Common/Types/Database/TableColumn"); - const origGetTableColumns = tableColumnModule.getTableColumns; - const DatabaseModels = require("Common/Models/DatabaseModels/Index").default; - const origFind = DatabaseModels.find; - const Permission = require("Common/Types/Permission").default; + /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ + const tableColumnModule: Record< + string, + unknown + > = require("Common/Types/Database/TableColumn"); + const origGetTableColumns: unknown = tableColumnModule.getTableColumns; + const DatabaseModels: Record = + require("Common/Models/DatabaseModels/Index").default; + const origFind: unknown = DatabaseModels.find; + const Permission: Record = + require("Common/Types/Permission").default; + /* eslint-enable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ try { - tableColumnModule.getTableColumns = () => ({ field1: {}, field2: {} }); + tableColumnModule.getTableColumns = (): Record< + string, + Record + > => { + return { field1: {}, field2: {} }; + }; - DatabaseModels.find = (fn) => { - function MockModel() { + DatabaseModels.find = (fn: (model: unknown) => boolean): unknown => { + function MockModel(this: Record): void { this.tableName = "MockTable"; - this.getColumnAccessControlForAllColumns = () => ({ - field1: { read: [Permission.CurrentUser] }, - field2: { read: [Permission.CurrentUser] }, - }); + this.getColumnAccessControlForAllColumns = (): Record< + string, + unknown + > => { + return { + field1: { read: [Permission.CurrentUser] }, + field2: { read: [Permission.CurrentUser] }, + }; + }; + } + const matches: boolean = fn(MockModel); + if (matches) { + return MockModel; } - const matches = fn(MockModel); - if (matches) return MockModel; return undefined; }; - const select = generateAllFieldsSelect("MockTable", "database"); + const select: JSONObject = generateAllFieldsSelect( + "MockTable", + "database", + ); expect(select).toEqual({ _id: true, createdAt: true, diff --git a/CLI/Tests/UtilityCommands.test.ts b/CLI/Tests/UtilityCommands.test.ts index 97cae6313f..a79e812106 100644 --- a/CLI/Tests/UtilityCommands.test.ts +++ b/CLI/Tests/UtilityCommands.test.ts @@ -1,13 +1,12 @@ import { Command } from "commander"; import { registerUtilityCommands } from "../Commands/UtilityCommands"; -import { registerResourceCommands } from "../Commands/ResourceCommands"; import * as ConfigManager from "../Core/ConfigManager"; import * as fs from "fs"; import * as path from "path"; import * as os from "os"; -const CONFIG_DIR = path.join(os.homedir(), ".oneuptime"); -const CONFIG_FILE = path.join(CONFIG_DIR, "config.json"); +const CONFIG_DIR: string = path.join(os.homedir(), ".oneuptime"); +const CONFIG_FILE: string = path.join(CONFIG_DIR, "config.json"); describe("UtilityCommands", () => { let originalConfigContent: string | null = null; @@ -46,7 +45,7 @@ describe("UtilityCommands", () => { }); function createProgram(): Command { - const program = new Command(); + const program: Command = new Command(); program.exitOverride(); program.configureOutput({ writeOut: () => {}, @@ -62,18 +61,18 @@ describe("UtilityCommands", () => { describe("version command", () => { it("should print version", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "version"]); expect(consoleLogSpy).toHaveBeenCalled(); // Should print a version string (either from package.json or fallback) - const versionArg = consoleLogSpy.mock.calls[0][0]; + const versionArg: string = consoleLogSpy.mock.calls[0][0]; expect(typeof versionArg).toBe("string"); }); }); describe("whoami command", () => { it("should show not authenticated when no credentials", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "whoami"]); expect(consoleLogSpy).toHaveBeenCalled(); }); @@ -85,7 +84,7 @@ describe("UtilityCommands", () => { apiKey: "abcdefghijklm", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "whoami"]); expect(consoleLogSpy).toHaveBeenCalledWith("URL: https://test.com"); @@ -102,7 +101,7 @@ describe("UtilityCommands", () => { apiKey: "abc", }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "whoami"]); expect(consoleLogSpy).toHaveBeenCalledWith("API Key: ****"); @@ -112,7 +111,7 @@ describe("UtilityCommands", () => { process.env["ONEUPTIME_API_KEY"] = "env-key-long-enough"; process.env["ONEUPTIME_URL"] = "https://env.com"; - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "whoami"]); expect(consoleLogSpy).toHaveBeenCalledWith("URL: https://env.com"); @@ -120,13 +119,13 @@ describe("UtilityCommands", () => { it("should handle whoami outer catch block", async () => { // Mock getCurrentContext to throw an unexpected error - const spy = jest + const spy: jest.SpyInstance = jest .spyOn(ConfigManager, "getCurrentContext") .mockImplementation(() => { throw new Error("Unexpected crash"); }); - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "whoami"]); expect(exitSpy).toHaveBeenCalledWith(1); @@ -137,12 +136,14 @@ describe("UtilityCommands", () => { process.env["ONEUPTIME_API_KEY"] = "env-key-long-enough"; process.env["ONEUPTIME_URL"] = "https://env.com"; - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync(["node", "test", "whoami"]); // Should NOT have a "Context:" call since no context is set - const contextCalls = consoleLogSpy.mock.calls.filter( - (call: any[]) => typeof call[0] === "string" && call[0].startsWith("Context:"), + const contextCalls: any[][] = consoleLogSpy.mock.calls.filter( + (call: any[]) => { + return typeof call[0] === "string" && call[0].startsWith("Context:"); + }, ); expect(contextCalls).toHaveLength(0); }); @@ -150,20 +151,22 @@ describe("UtilityCommands", () => { describe("resources command", () => { it("should list all resources", async () => { - // We need registerResourceCommands for discoverResources to work - // but discoverResources is imported directly, so it should work - const program = createProgram(); + /* + * We need registerResourceCommands for discoverResources to work + * but discoverResources is imported directly, so it should work + */ + const program: Command = createProgram(); await program.parseAsync(["node", "test", "resources"]); expect(consoleLogSpy).toHaveBeenCalled(); // Should show total count - const lastCall = + const lastCall: string = consoleLogSpy.mock.calls[consoleLogSpy.mock.calls.length - 1][0]; expect(lastCall).toContain("Total:"); }); it("should filter by type", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", @@ -176,7 +179,7 @@ describe("UtilityCommands", () => { }); it("should show message when filter returns no results", async () => { - const program = createProgram(); + const program: Command = createProgram(); await program.parseAsync([ "node", "test", diff --git a/CLI/Utils/SelectFieldGenerator.ts b/CLI/Utils/SelectFieldGenerator.ts index 8113859e89..1d6cc49803 100644 --- a/CLI/Utils/SelectFieldGenerator.ts +++ b/CLI/Utils/SelectFieldGenerator.ts @@ -34,9 +34,7 @@ export function generateAllFieldsSelect( ): JSONObject { try { if (modelType === "database") { - const ModelClass: - | (new () => BaseModel) - | undefined = DatabaseModels.find( + const ModelClass: (new () => BaseModel) | undefined = DatabaseModels.find( (Model: new () => BaseModel): boolean => { try { const instance: BaseModel = new Model(); @@ -85,18 +83,15 @@ export function generateAllFieldsSelect( } if (modelType === "analytics") { - const ModelClass: - | (new () => AnalyticsBaseModel) - | undefined = AnalyticsModels.find( - (Model: new () => AnalyticsBaseModel): boolean => { + const ModelClass: (new () => AnalyticsBaseModel) | undefined = + AnalyticsModels.find((Model: new () => AnalyticsBaseModel): boolean => { try { const instance: AnalyticsBaseModel = new Model(); return instance.tableName === tableName; } catch { return false; } - }, - ); + }); if (!ModelClass) { return getDefaultSelect(); diff --git a/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx b/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx index ffefb38b66..29f3383fa5 100644 --- a/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx +++ b/Dashboard/src/Pages/Monitor/DisabledMonitors.tsx @@ -3,7 +3,9 @@ import ProjectUtil from "Common/UI/Utils/Project"; import PageComponentProps from "../PageComponentProps"; import React, { FunctionComponent, ReactElement } from "react"; -const DisabledMonitors: FunctionComponent = (): ReactElement => { +const DisabledMonitors: FunctionComponent< + PageComponentProps +> = (): ReactElement => { return ( = (): ReactElement => { +const NotOperationalMonitors: FunctionComponent< + PageComponentProps +> = (): ReactElement => { return ( = (): ReactElement => { +const DisabledMonitors: FunctionComponent< + PageComponentProps +> = (): ReactElement => { return ( = (): ReactElement => { +const DisabledMonitors: FunctionComponent< + PageComponentProps +> = (): ReactElement => { return ( = (): ReactElement => { +const IncomingCallPhoneNumbers: FunctionComponent< + PageComponentProps +> = (): ReactElement => { return ; }; diff --git a/MobileApp/src/api/types.ts b/MobileApp/src/api/types.ts index b6baa9da88..35ec3bea13 100644 --- a/MobileApp/src/api/types.ts +++ b/MobileApp/src/api/types.ts @@ -39,7 +39,9 @@ type OnCallDutyPolicy = InstanceType; type OnCallDutyPolicyEscalationRule = InstanceType< typeof OnCallDutyPolicyEscalationRuleModel >; -type OnCallDutyPolicySchedule = InstanceType; +type OnCallDutyPolicySchedule = InstanceType< + typeof OnCallDutyPolicyScheduleModel +>; type Project = InstanceType; type Team = InstanceType; type User = InstanceType; @@ -90,7 +92,12 @@ type IncidentItemFromCommon = RequiredModelFields< export interface IncidentItem extends Omit< IncidentItemFromCommon, - "declaredAt" | "createdAt" | "currentIncidentState" | "incidentSeverity" | "monitors" | "projectId" + | "declaredAt" + | "createdAt" + | "currentIncidentState" + | "incidentSeverity" + | "monitors" + | "projectId" > { rootCause?: string; declaredAt: string; @@ -115,7 +122,11 @@ type AlertItemFromCommon = RequiredModelFields< export interface AlertItem extends Omit< AlertItemFromCommon, - "createdAt" | "currentAlertState" | "alertSeverity" | "monitor" | "projectId" + | "createdAt" + | "currentAlertState" + | "alertSeverity" + | "monitor" + | "projectId" > { rootCause?: string; createdAt: string; @@ -129,7 +140,12 @@ export interface AlertItem export interface IncidentState extends RequiredModelFields< IncidentStateModel, - "_id" | "name" | "isResolvedState" | "isAcknowledgedState" | "isCreatedState" | "order" + | "_id" + | "name" + | "isResolvedState" + | "isAcknowledgedState" + | "isCreatedState" + | "order" > { color: ColorField; } @@ -137,7 +153,12 @@ export interface IncidentState export interface AlertState extends RequiredModelFields< AlertStateModel, - "_id" | "name" | "isResolvedState" | "isAcknowledgedState" | "isCreatedState" | "order" + | "_id" + | "name" + | "isResolvedState" + | "isAcknowledgedState" + | "isCreatedState" + | "order" > { color: ColorField; } @@ -170,7 +191,11 @@ type IncidentEpisodeItemFromCommon = RequiredModelFields< export interface IncidentEpisodeItem extends Omit< IncidentEpisodeItemFromCommon, - "createdAt" | "declaredAt" | "currentIncidentState" | "incidentSeverity" | "projectId" + | "createdAt" + | "declaredAt" + | "currentIncidentState" + | "incidentSeverity" + | "projectId" > { rootCause?: string; createdAt: string; @@ -212,7 +237,8 @@ type NoteItemFromCommon = RequiredModelFields< "_id" | "note" | "createdAt" >; -export interface NoteItem extends Omit { +export interface NoteItem + extends Omit { createdAt: string; createdByUser: | (RequiredModelFields & { @@ -222,9 +248,13 @@ export interface NoteItem extends Omit; +type FeedItemFromCommon = RequiredModelFields< + AlertFeed | IncidentFeed, + "_id" | "feedInfoInMarkdown" | "createdAt" +>; -export interface FeedItem extends Omit { +export interface FeedItem + extends Omit { feedInfoInMarkdown: string; moreInformationInMarkdown?: string; displayColor: ColorField; diff --git a/MobileApp/src/components/AlertCard.tsx b/MobileApp/src/components/AlertCard.tsx index a6b8f023aa..35f8ef0a62 100644 --- a/MobileApp/src/components/AlertCard.tsx +++ b/MobileApp/src/components/AlertCard.tsx @@ -79,7 +79,10 @@ export default function AlertCard({ /> ALERT diff --git a/MobileApp/src/components/EpisodeCard.tsx b/MobileApp/src/components/EpisodeCard.tsx index 4d68a97889..b24463a527 100644 --- a/MobileApp/src/components/EpisodeCard.tsx +++ b/MobileApp/src/components/EpisodeCard.tsx @@ -111,7 +111,10 @@ export default function EpisodeCard( /> {type === "incident" ? "INCIDENT EPISODE" : "ALERT EPISODE"} @@ -131,7 +134,8 @@ export default function EpisodeCard( letterSpacing: 0.2, }} > - {episode.episodeNumberWithPrefix || `#${episode.episodeNumber}`} + {episode.episodeNumberWithPrefix || + `#${episode.episodeNumber}`} diff --git a/MobileApp/src/components/IncidentCard.tsx b/MobileApp/src/components/IncidentCard.tsx index 865377aba0..8b54047857 100644 --- a/MobileApp/src/components/IncidentCard.tsx +++ b/MobileApp/src/components/IncidentCard.tsx @@ -87,7 +87,10 @@ export default function IncidentCard({ /> INCIDENT diff --git a/MobileApp/src/components/Logo.tsx b/MobileApp/src/components/Logo.tsx index a6fb01b84f..bae9e30e5b 100644 --- a/MobileApp/src/components/Logo.tsx +++ b/MobileApp/src/components/Logo.tsx @@ -13,7 +13,5 @@ export default function Logo({ }: LogoProps): React.JSX.Element { const logoSvg: string = ``; - return ( - - ); + return ; } diff --git a/MobileApp/src/components/MarkdownContent.tsx b/MobileApp/src/components/MarkdownContent.tsx index 2e35e7adf2..f730ca5178 100644 --- a/MobileApp/src/components/MarkdownContent.tsx +++ b/MobileApp/src/components/MarkdownContent.tsx @@ -96,4 +96,4 @@ export default function MarkdownContent({ {markdownText} ); -} \ No newline at end of file +} diff --git a/MobileApp/src/components/NotesSection.tsx b/MobileApp/src/components/NotesSection.tsx index 53a9967aea..a485e31816 100644 --- a/MobileApp/src/components/NotesSection.tsx +++ b/MobileApp/src/components/NotesSection.tsx @@ -75,7 +75,9 @@ export default function NotesSection({ backgroundColor: theme.colors.backgroundElevated, borderWidth: 1, borderColor: theme.colors.borderGlass, - shadowColor: theme.isDark ? "#000" : theme.colors.accentGradientMid, + shadowColor: theme.isDark + ? "#000" + : theme.colors.accentGradientMid, shadowOpacity: theme.isDark ? 0.16 : 0.06, shadowOffset: { width: 0, height: 5 }, shadowRadius: 10, diff --git a/MobileApp/src/components/RootCauseCard.tsx b/MobileApp/src/components/RootCauseCard.tsx index be64c91d70..6db238ca45 100644 --- a/MobileApp/src/components/RootCauseCard.tsx +++ b/MobileApp/src/components/RootCauseCard.tsx @@ -35,4 +35,4 @@ export default function RootCauseCard({ ); -} \ No newline at end of file +} diff --git a/MobileApp/src/components/SectionHeader.tsx b/MobileApp/src/components/SectionHeader.tsx index a0384eb73f..4e1b19783b 100644 --- a/MobileApp/src/components/SectionHeader.tsx +++ b/MobileApp/src/components/SectionHeader.tsx @@ -23,7 +23,11 @@ export default function SectionHeader({ borderColor: theme.colors.borderGlass, }} > - + ({ diff --git a/MobileApp/src/hooks/useAllProjectOnCallPolicies.ts b/MobileApp/src/hooks/useAllProjectOnCallPolicies.ts index 174d46641f..db4abe12b0 100644 --- a/MobileApp/src/hooks/useAllProjectOnCallPolicies.ts +++ b/MobileApp/src/hooks/useAllProjectOnCallPolicies.ts @@ -17,7 +17,10 @@ interface UseAllProjectOnCallPoliciesResult { refetch: () => Promise; } -function getEntityId(entity?: { _id?: string; id?: string }): string | undefined { +function getEntityId(entity?: { + _id?: string; + id?: string; +}): string | undefined { return entity?._id ?? entity?.id; } @@ -33,7 +36,8 @@ function toAssignments( projectName: project.name, policyId: getEntityId(rule.onCallDutyPolicy), policyName: rule.onCallDutyPolicy?.name ?? "Unknown policy", - escalationRuleName: rule.onCallDutyPolicyEscalationRule?.name ?? "Unknown rule", + escalationRuleName: + rule.onCallDutyPolicyEscalationRule?.name ?? "Unknown rule", assignmentType: "user", assignmentDetail: "You are directly assigned", }); @@ -45,7 +49,8 @@ function toAssignments( projectName: project.name, policyId: getEntityId(rule.onCallDutyPolicy), policyName: rule.onCallDutyPolicy?.name ?? "Unknown policy", - escalationRuleName: rule.onCallDutyPolicyEscalationRule?.name ?? "Unknown rule", + escalationRuleName: + rule.onCallDutyPolicyEscalationRule?.name ?? "Unknown rule", assignmentType: "team", assignmentDetail: `Via team: ${rule.team?.name ?? "Unknown"}`, }); @@ -57,7 +62,8 @@ function toAssignments( projectName: project.name, policyId: getEntityId(rule.onCallDutyPolicy), policyName: rule.onCallDutyPolicy?.name ?? "Unknown policy", - escalationRuleName: rule.onCallDutyPolicyEscalationRule?.name ?? "Unknown rule", + escalationRuleName: + rule.onCallDutyPolicyEscalationRule?.name ?? "Unknown rule", assignmentType: "schedule", assignmentDetail: `Via schedule: ${rule.onCallDutyPolicySchedule?.name ?? "Unknown"}`, }); @@ -107,15 +113,19 @@ export function useAllProjectOnCallPolicies(): UseAllProjectOnCallPoliciesResult const projects: ProjectOnCallAssignments[] = []; - results.forEach((result: PromiseSettledResult) => { - if (result.status === "fulfilled" && result.value) { - projects.push(result.value); - } - }); + results.forEach( + (result: PromiseSettledResult) => { + if (result.status === "fulfilled" && result.value) { + projects.push(result.value); + } + }, + ); - return projects.sort((a: ProjectOnCallAssignments, b: ProjectOnCallAssignments) => { - return a.projectName.localeCompare(b.projectName); - }); + return projects.sort( + (a: ProjectOnCallAssignments, b: ProjectOnCallAssignments) => { + return a.projectName.localeCompare(b.projectName); + }, + ); }, }); diff --git a/MobileApp/src/navigation/MainTabNavigator.tsx b/MobileApp/src/navigation/MainTabNavigator.tsx index d9a7693c12..2bb60e7156 100644 --- a/MobileApp/src/navigation/MainTabNavigator.tsx +++ b/MobileApp/src/navigation/MainTabNavigator.tsx @@ -74,7 +74,9 @@ export default function MainTabNavigator(): React.JSX.Element { height: Platform.OS === "ios" ? 78 : 68, paddingBottom: Platform.OS === "ios" ? 18 : 10, paddingTop: 10, - shadowColor: theme.isDark ? "#000000" : theme.colors.accentGradientMid, + shadowColor: theme.isDark + ? "#000000" + : theme.colors.accentGradientMid, shadowOpacity: theme.isDark ? 0.35 : 0.12, shadowOffset: { width: 0, height: 8 }, shadowRadius: 18, diff --git a/MobileApp/src/navigation/OnCallStackNavigator.tsx b/MobileApp/src/navigation/OnCallStackNavigator.tsx index 299821d850..e37d12d265 100644 --- a/MobileApp/src/navigation/OnCallStackNavigator.tsx +++ b/MobileApp/src/navigation/OnCallStackNavigator.tsx @@ -5,8 +5,9 @@ import { useTheme } from "../theme"; import MyOnCallPoliciesScreen from "../screens/MyOnCallPoliciesScreen"; import type { OnCallStackParamList } from "./types"; -const Stack: ReturnType> = - createNativeStackNavigator(); +const Stack: ReturnType< + typeof createNativeStackNavigator +> = createNativeStackNavigator(); export default function OnCallStackNavigator(): React.JSX.Element { const { theme } = useTheme(); diff --git a/MobileApp/src/screens/AlertDetailScreen.tsx b/MobileApp/src/screens/AlertDetailScreen.tsx index 689529814c..4c5273e614 100644 --- a/MobileApp/src/screens/AlertDetailScreen.tsx +++ b/MobileApp/src/screens/AlertDetailScreen.tsx @@ -185,7 +185,8 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { const isResolved: boolean = resolveState?._id === currentStateId; const isAcknowledged: boolean = acknowledgeState?._id === currentStateId; const rootCauseTextRaw: string = toPlainText(alert.rootCause); - const rootCauseText: string | undefined = rootCauseTextRaw.trim() || undefined; + const rootCauseText: string | undefined = + rootCauseTextRaw.trim() || undefined; const descriptionText: string = toPlainText(alert.description); return ( @@ -373,78 +374,81 @@ export default function AlertDetailScreen({ route }: Props): React.JSX.Element { }} > - {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - activeOpacity={0.85} - accessibilityRole="button" - accessibilityLabel="Acknowledge alert" - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - - ) : null} + {!isAcknowledged && !isResolved && acknowledgeState ? ( + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + activeOpacity={0.85} + accessibilityRole="button" + accessibilityLabel="Acknowledge alert" + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} - {resolveState ? ( - { - return handleStateChange(resolveState._id, resolveState.name); - }} - disabled={changingState} - activeOpacity={0.85} - accessibilityRole="button" - accessibilityLabel="Resolve alert" - > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - - ) : null} + {resolveState ? ( + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + activeOpacity={0.85} + accessibilityRole="button" + accessibilityLabel="Resolve alert" + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} diff --git a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx index 01ffdad602..811c17ff22 100644 --- a/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/AlertEpisodeDetailScreen.tsx @@ -193,7 +193,8 @@ export default function AlertEpisodeDetailScreen({ const isResolved: boolean = resolveState?._id === currentStateId; const isAcknowledged: boolean = acknowledgeState?._id === currentStateId; const rootCauseTextRaw: string = toPlainText(episode.rootCause); - const rootCauseText: string | undefined = rootCauseTextRaw.trim() || undefined; + const rootCauseText: string | undefined = + rootCauseTextRaw.trim() || undefined; const descriptionText: string = toPlainText(episode.description); return ( @@ -381,71 +382,74 @@ export default function AlertEpisodeDetailScreen({ }} > - {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - - ) : null} - {resolveState ? ( - { - return handleStateChange(resolveState._id, resolveState.name); - }} - disabled={changingState} - > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - - ) : null} + {!isAcknowledged && !isResolved && acknowledgeState ? ( + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} + {resolveState ? ( + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} diff --git a/MobileApp/src/screens/HomeScreen.tsx b/MobileApp/src/screens/HomeScreen.tsx index 11f44f3cab..71e51d2978 100644 --- a/MobileApp/src/screens/HomeScreen.tsx +++ b/MobileApp/src/screens/HomeScreen.tsx @@ -56,7 +56,10 @@ function StatCard({ accessibilityRole="button" > - {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - activeOpacity={0.85} - accessibilityRole="button" - accessibilityLabel="Acknowledge incident" - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - - ) : null} + {!isAcknowledged && !isResolved && acknowledgeState ? ( + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + activeOpacity={0.85} + accessibilityRole="button" + accessibilityLabel="Acknowledge incident" + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} - {resolveState ? ( - { - return handleStateChange(resolveState._id, resolveState.name); - }} - disabled={changingState} - activeOpacity={0.85} - accessibilityRole="button" - accessibilityLabel="Resolve incident" - > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - - ) : null} + {resolveState ? ( + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + activeOpacity={0.85} + accessibilityRole="button" + accessibilityLabel="Resolve incident" + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null} diff --git a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx index d094a2163f..ac0d1fe755 100644 --- a/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx +++ b/MobileApp/src/screens/IncidentEpisodeDetailScreen.tsx @@ -202,7 +202,8 @@ export default function IncidentEpisodeDetailScreen({ const isResolved: boolean = resolveState?._id === currentStateId; const isAcknowledged: boolean = acknowledgeState?._id === currentStateId; const rootCauseTextRaw: string = toPlainText(episode.rootCause); - const rootCauseText: string | undefined = rootCauseTextRaw.trim() || undefined; + const rootCauseText: string | undefined = + rootCauseTextRaw.trim() || undefined; const descriptionText: string = toPlainText(episode.description); return ( @@ -388,71 +389,74 @@ export default function IncidentEpisodeDetailScreen({ }} > - {!isAcknowledged && !isResolved && acknowledgeState ? ( - { - return handleStateChange( - acknowledgeState._id, - acknowledgeState.name, - ); - }} - disabled={changingState} - > - {changingState ? ( - - ) : ( - <> - - - Acknowledge - - - )} - - ) : null} - {resolveState ? ( - { - return handleStateChange(resolveState._id, resolveState.name); - }} - disabled={changingState} - > - {changingState ? ( - - ) : ( - <> - - - Resolve - - - )} - - ) : null} + {!isAcknowledged && !isResolved && acknowledgeState ? ( + { + return handleStateChange( + acknowledgeState._id, + acknowledgeState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Acknowledge + + + )} + + ) : null} + {resolveState ? ( + { + return handleStateChange( + resolveState._id, + resolveState.name, + ); + }} + disabled={changingState} + > + {changingState ? ( + + ) : ( + <> + + + Resolve + + + )} + + ) : null}