40 examples

Missing null check

Failure to verify null values, causing runtime exceptions.

[ FAQ1 ]

What is a missing null check?

A missing null check occurs when code attempts to use or access properties of an object or value without first verifying if it's null or undefined. This typically results in runtime errors such as null pointer exceptions or type errors, causing the application to crash or behave unpredictably. Missing null checks are common pitfalls in programming, especially when handling optional or dynamically retrieved data, where the presence of valid values isn't guaranteed.
[ FAQ2 ]

How to fix missing null checks

To fix missing null checks, always explicitly verify values for null or undefined before accessing their properties or methods. Employ safe coding practices like conditional checks (if (value !== null)), optional chaining (value?.property), or null-coalescing operators to handle potentially null or undefined values gracefully. Regularly test code paths with both valid and null inputs to proactively identify and address missing null checks. Utilizing static analysis tools or linters can help automatically detect and flag code missing essential null or undefined checks, significantly improving application stability.
diff block
import { Loader2 } from "lucide-react";
import "@/styles/report.css";
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, ReferenceLine } from 'recharts';
-import { YearlyPopulationGraphDataPointSchema, PopulationPyramidDataPointSchema } from "@/schemas/views/market-research-schema";
+import { MarketResearch, Radius } from "@/schemas/views/market-research-schema";
-export const PopulationGraphs = ({yearlyPopulationData, populationPyramidData, endYear}: {yearlyPopulationData: YearlyPopulationGraphDataPointSchema[], populationPyramidData: PopulationPyramidDataPointSchema[], endYear: number}) => {
+const formatRadius = (radius: Radius) => {
+ return radius === 1 ? "one" : radius === 3 ? "three" : "five";
+};
+
+const years = ["2020", "2021", "2022", "2023", "2024"];
+const ageGroups = {
+ age_0_4: "0-4",
+ age_5_9: "5-9",
+ age_10_14: "10-14",
+ age_15_19: "15-19",
+ age_20_24: "20-24",
+ age_25_29: "25-29",
+ age_30_34: "30-34",
+ age_35_39: "35-39",
+ age_40_44: "40-44",
+ age_45_49: "45-49",
+ age_50_54: "50-54",
+ age_55_59: "55-59",
+ age_60_64: "60-64",
+ age_65_69: "65-69",
+ age_70_74: "70-74",
+ age_75_79: "75-79",
+ age_80_84: "80-84",
+ age_85_plus: "85+"
+};
+export const PopulationGraphs = ({ marketData, radius }: {marketData: MarketResearch, radius: Radius}) => {
+
+ const dataInRadius = marketData[`${formatRadius(radius)}_mile_attributes`];
+ const yearlyPopulationData = years.map((year) => ({
+ year: parseInt(year),
+ population: dataInRadius?.population_data?.yearly_populations[`total_population_${year}` as keyof typeof dataInRadius.population_data.yearly_populations]
+ }));
+ const populationPyramidData = Object.entries(dataInRadius?.population_data?.five_year_age_brackets).map(([ageGroup, count]) => ({
+ ageGroup: ageGroups[ageGroup as keyof typeof ageGroups],
+ count: count || null
+ })).reverse();
Greptile
greptile
logic: Missing null check on five_year_age_brackets. Object.entries() will throw if undefined
suggested fix
+ const populationPyramidData = Object.entries(dataInRadius?.population_data?.five_year_age_brackets || {}).map(([ageGroup, count]) => ({
ageGroup: ageGroups[ageGroup as keyof typeof ageGroups],
count: count || null
})).reverse();
diff block
+import { CacheOptions } from '@posthog/plugin-scaffold'
+import { Pool as GenericPool } from 'generic-pool'
+import Redis from 'ioredis'
+
+import { RedisOperationError } from '../../utils/db/error'
+import { timeoutGuard } from '../../utils/db/utils'
+import { instrumentQuery } from '../../utils/metrics'
+import { tryTwice } from '../../utils/utils'
+
+/** The recommended way of accessing the database. */
+export class RedisHelpers {
+ /** Redis used for various caches. */
+ redisPool: GenericPool<Redis.Redis>
+
+ constructor(redisPool: GenericPool<Redis.Redis>) {
+ this.redisPool = redisPool
+ }
+
+ private instrumentRedisQuery<T>(
+ operationName: string,
+ tag: string | undefined,
+ logContext: Record<string, string | string[] | number>,
+ runQuery: (client: Redis.Redis) => Promise<T>
+ ): Promise<T> {
+ return instrumentQuery(operationName, tag, async () => {
+ let client: Redis.Redis
+ const timeout = timeoutGuard(`${operationName} delayed. Waiting over 30 sec.`, logContext)
+ try {
+ client = await this.redisPool.acquire()
+ } catch (error) {
+ throw new RedisOperationError('Failed to acquire redis client from pool', error, operationName)
+ }
+
+ // Don't use a single try/catch/finally for this, as there are 2 potential errors that could be thrown
+ // (error and cleanup) and we want to be explicit about which one we choose, rather than relying on
+ // "what happens when you throw in a finally block".
+ // We explicitly want to throw the error from the operation if there is one, prioritising it over any errors
+ // from the cleanup
+ let operationResult: { value: T } | { error: Error }
+ let cleanupError: Error | undefined
+
+ try {
+ operationResult = { value: await runQuery(client) }
+ } catch (error) {
+ operationResult = { error }
+ }
+
+ try {
+ clearTimeout(timeout)
+ await this.redisPool.release(client)
+ } catch (error) {
+ cleanupError = error
+ }
+
+ if ('error' in operationResult) {
+ throw new RedisOperationError(
+ `${operationName} failed for ${JSON.stringify(logContext)}`,
+ operationResult.error,
+ operationName,
+ logContext
+ )
+ }
+ if (cleanupError) {
+ throw new RedisOperationError('Failed to release redis client from pool', cleanupError, operationName)
+ }
+ return operationResult.value
+ })
+ }
+
+ public redisGet<T = unknown>(
+ key: string,
+ defaultValue: T,
+ tag: string,
+ options: CacheOptions = {}
+ ): Promise<T | null> {
+ const { jsonSerialize = true } = options
+ return this.instrumentRedisQuery('query.redisGet', tag, { key }, async (client) => {
+ try {
+ const value = await tryTwice(
+ async () => await client.get(key),
+ `Waited 5 sec to get redis key: ${key}, retrying once!`
+ )
+ if (typeof value === 'undefined' || value === null) {
+ return defaultValue
+ }
+ return value ? (jsonSerialize ? JSON.parse(value) : value) : null
+ } catch (error) {
+ if (error instanceof SyntaxError) {
+ // invalid JSON
+ return null
+ } else {
+ throw error
+ }
+ }
+ })
+ }
+
+ public redisGetBuffer(key: string, tag: string): Promise<Buffer | null> {
+ return this.instrumentRedisQuery('query.redisGetBuffer', tag, { key }, async (client) => {
+ return await tryTwice(
+ async () => await client.getBuffer(key),
+ `Waited 5 sec to get redis key: ${key}, retrying once!`
+ )
+ })
+ }
+
+ public redisSetBuffer(key: string, value: Buffer, tag: string, ttlSeconds?: number): Promise<void> {
+ return this.instrumentRedisQuery('query.redisSetBuffer', tag, { key }, async (client) => {
+ if (ttlSeconds) {
+ await client.setBuffer(key, value, 'EX', ttlSeconds)
+ } else {
+ await client.setBuffer(key, value)
+ }
+ })
+ }
+
+ public redisSetNX(
+ key: string,
+ value: unknown,
+ tag: string,
+ ttlSeconds?: number,
+ options: CacheOptions = {}
+ ): Promise<'OK' | null> {
+ const { jsonSerialize = true } = options
+
+ return this.instrumentRedisQuery('query.redisSetNX', tag, { key }, async (client) => {
+ const serializedValue = jsonSerialize ? JSON.stringify(value) : (value as string)
+ if (ttlSeconds) {
+ return await client.set(key, serializedValue, 'EX', ttlSeconds, 'NX')
+ } else {
+ return await client.set(key, serializedValue, 'NX')
+ }
+ })
+ }
+
+ public redisSetMulti(kv: Array<[string, unknown]>, ttlSeconds?: number, options: CacheOptions = {}): Promise<void> {
+ const { jsonSerialize = true } = options
+
+ return this.instrumentRedisQuery('query.redisSet', undefined, { keys: kv.map((x) => x[0]) }, async (client) => {
+ let pipeline = client.multi()
+ for (const [key, value] of kv) {
+ const serializedValue = jsonSerialize ? JSON.stringify(value) : (value as string)
+ if (ttlSeconds) {
+ pipeline = pipeline.set(key, serializedValue, 'EX', ttlSeconds)
+ } else {
+ pipeline = pipeline.set(key, serializedValue)
+ }
+ }
+ await pipeline.exec()
+ })
+ }
+
+ public redisIncr(key: string): Promise<number> {
+ return this.instrumentRedisQuery('query.redisIncr', undefined, { key }, async (client) => {
+ return await client.incr(key)
+ })
+ }
+
+ public redisExpire(key: string, ttlSeconds: number): Promise<boolean> {
+ return this.instrumentRedisQuery('query.redisExpire', undefined, { key }, async (client) => {
+ return (await client.expire(key, ttlSeconds)) === 1
+ })
+ }
+
+ public redisLPush(key: string, value: unknown, options: CacheOptions = {}): Promise<number> {
+ const { jsonSerialize = true } = options
+
+ return this.instrumentRedisQuery('query.redisLPush', undefined, { key }, async (client) => {
+ const serializedValue = jsonSerialize ? JSON.stringify(value) : (value as string | string[])
+ return await client.lpush(key, serializedValue)
+ })
+ }
+
+ public redisLRange(key: string, startIndex: number, endIndex: number, tag?: string): Promise<string[]> {
+ return this.instrumentRedisQuery('query.redisLRange', tag, { key, startIndex, endIndex }, async (client) => {
+ return await client.lrange(key, startIndex, endIndex)
+ })
+ }
+
+ public redisLLen(key: string): Promise<number> {
+ return this.instrumentRedisQuery('query.redisLLen', undefined, { key }, async (client) => {
+ return await client.llen(key)
+ })
+ }
+
+ public redisBRPop(key1: string, key2: string): Promise<[string, string]> {
+ return this.instrumentRedisQuery('query.redisBRPop', undefined, { key1, key2 }, async (client) => {
+ return await client.brpop(key1, key2)
+ })
+ }
+
+ public redisLRem(key: string, count: number, elementKey: string): Promise<number> {
+ return this.instrumentRedisQuery(
+ 'query.redisLRem',
+ undefined,
+ {
+ key,
+ count,
+ elementKey,
+ },
+ async (client) => {
+ return await client.lrem(key, count, elementKey)
+ }
+ )
+ }
+
+ public redisLPop(key: string, count: number): Promise<string[]> {
+ return this.instrumentRedisQuery(
+ 'query.redisLPop',
+ undefined,
+ {
+ key,
+ count,
+ },
+ async (client) => {
+ return await client.lpop(key, count)
+ }
+ )
+ }
+
+ public redisSAddAndSCard(key: string, value: Redis.ValueType, ttlSeconds?: number): Promise<number> {
+ return this.instrumentRedisQuery('query.redisSAddAndSCard', undefined, { key }, async (client) => {
+ const multi = client.multi()
+ multi.sadd(key, value)
+ if (ttlSeconds) {
+ multi.expire(key, ttlSeconds)
+ }
+ multi.scard(key)
+ const results = await multi.exec()
+ const scardResult = ttlSeconds ? results[2] : results[1]
+ return scardResult[1]
Greptile
greptile
logic: Missing null check for results from multi.exec() which could be null if transaction fails
suggested fix
const results = await multi.exec()
+ if (!results) {
+ throw new Error('Redis transaction failed')
}
const scardResult = ttlSeconds ? results[2] : results[1]
return scardResult[1]
diff block
def channels(self, request: Request, *args: Any, **kwargs: Any) -> Response:
instance = self.get_object()
slack = SlackIntegration(instance)
- authed_user = instance.config["authed_user"]["id"]
+ should_include_private_channels: bool = instance.created_by_id == request.user.id
+ authed_user: str = instance.config["authed_user"]["id"]
Greptile
greptile
logic: Missing null check for instance.config['authed_user'] - could throw KeyError
suggested fix
+ if "authed_user" not in instance.config:
+ raise ValidationError("Missing authed_user in integration config")
authed_user: str = instance.config["authed_user"]["id"]
diff block
transform(chunk, controller) {
const chunkType = chunk.type;
switch (chunkType) {
+ case "reasoning": {
+ controller.enqueue({
+ type: AssistantStreamChunkType.ReasoningDelta,
+ value: chunk.textDelta,
+ });
+ break;
+ }
Greptile
greptile
logic: missing null check for chunk.textDelta like in text-delta case. Could cause issues if textDelta is undefined
suggested fix
case "reasoning": {
+ if (!chunk.textDelta) break; // ignore empty text deltas
controller.enqueue({
type: AssistantStreamChunkType.ReasoningDelta,
value: chunk.textDelta,
});
break;
}
diff block
+import {
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ Type,
+} from '@nestjs/common';
+import { GqlExecutionContext } from '@nestjs/graphql';
+
+import { SettingsFeatures } from 'twenty-shared';
+
+import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
+import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
+import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
+
+export const SettingsPermissionsGuard = (
+ requiredPermission: SettingsFeatures,
+): Type<CanActivate> => {
+ @Injectable()
+ class SettingsGuardMixin extends SettingsPermissions {
+ constructor(
+ featureFlagService: FeatureFlagService,
+ permissionsService: PermissionsService,
+ ) {
+ super(requiredPermission, featureFlagService, permissionsService);
+ }
+ }
+
+ return SettingsGuardMixin;
+};
+
+@Injectable()
+class SettingsPermissions implements CanActivate {
+ constructor(
+ protected readonly requiredPermission: SettingsFeatures,
+ protected readonly featureFlagService: FeatureFlagService,
+ protected readonly permissionsService: PermissionsService,
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise<boolean> {
+ const ctx = GqlExecutionContext.create(context);
+
+ const workspaceId = ctx.getContext().req.workspace.id;
+
+ const permissionsEnabled = await this.featureFlagService.isFeatureEnabled(
+ FeatureFlagKey.IsPermissionsEnabled,
+ workspaceId,
+ );
+
+ if (!permissionsEnabled) {
+ return true;
+ }
+
+ const userWorkspaceId = ctx.getContext().req.userWorkspaceId;
Greptile
greptile
logic: Missing null check for userWorkspaceId. Could throw if context/req/userWorkspaceId is undefined.
diff block
+import {
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ Type,
+} from '@nestjs/common';
+import { GqlExecutionContext } from '@nestjs/graphql';
+
+import { SettingsFeatures } from 'twenty-shared';
+
+import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
+import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
+import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
+
+export const SettingsPermissionsGuard = (
+ requiredPermission: SettingsFeatures,
+): Type<CanActivate> => {
+ @Injectable()
+ class SettingsGuardMixin extends SettingsPermissions {
+ constructor(
+ featureFlagService: FeatureFlagService,
+ permissionsService: PermissionsService,
+ ) {
+ super(requiredPermission, featureFlagService, permissionsService);
+ }
+ }
+
+ return SettingsGuardMixin;
+};
+
+@Injectable()
+class SettingsPermissions implements CanActivate {
+ constructor(
+ protected readonly requiredPermission: SettingsFeatures,
+ protected readonly featureFlagService: FeatureFlagService,
+ protected readonly permissionsService: PermissionsService,
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise<boolean> {
+ const ctx = GqlExecutionContext.create(context);
+
+ const workspaceId = ctx.getContext().req.workspace.id;
Greptile
greptile
logic: Missing null check for workspace.id. Could throw if context/req/workspace is undefined.
diff block
async append({ parentId, message }: ExportedMessageRepositoryItem) {
const { remoteId } = await this.threadListItemRuntime.initialize();
- const task = this.cloudRef.current.threads.messages
+ const cloud = this.cloudRef.current;
Greptile
greptile
logic: Missing null check for cloudRef.current could cause runtime errors
suggested fix
const cloud = this.cloudRef.current;
+ if (!cloud) throw new Error('Cloud reference is not initialized');
diff block
+import { Element as HASTElement, Parent as HASTParent } from 'hast'
+import { fromDom } from 'hast-util-from-dom'
+
+type SimplifyBlocksOptions = {
+ orderedListItemBlockTypes: Set<string>
+ unorderedListItemBlockTypes: Set<string>
+}
+
+/**
+ * Rehype plugin which converts the HTML output string rendered by BlockNote into a simplified structure which better
+ * follows HTML standards. It does several things:
+ * - Removes all block related div elements, leaving only the actual content inside the block.
+ * - Lifts nested blocks to a higher level for all block types that don't represent list items.
+ * - Wraps blocks which represent list items in corresponding ul/ol HTML elements and restructures them to comply
+ * with HTML list structure.
+ * @param options Options for specifying which block types represent ordered and unordered list items.
+ */
+export function simplifyBlocks(options: SimplifyBlocksOptions) {
+ const listItemBlockTypes = new Set<string>([
+ ...options.orderedListItemBlockTypes,
+ ...options.unorderedListItemBlockTypes,
+ ])
+
+ const simplifyBlocksHelper = (tree: HASTParent) => {
+ let numChildElements = tree.children.length
+ let activeList: HASTElement | undefined
+
+ for (let i = 0; i < numChildElements; i++) {
+ const blockOuter = tree.children[i] as HASTElement
+ const blockContainer = blockOuter.children[0] as HASTElement
+ const blockContent = blockContainer.children[0] as HASTElement
+ const blockGroup = blockContainer.children.length === 2 ? (blockContainer.children[1] as HASTElement) : null
Greptile
greptile
logic: Missing null checks on children array access could cause runtime errors if the expected structure is not present
diff block
}
}
} else if (mIsScriptTree) {
- mpHost->getScriptUnit()->reParentScript(mChildID, mOldParentID, newParentID, parentPosition, childPosition);
+ if (mOldParentID == newParentID) {
+ return;
+ }
+ if (!mParentItem) {
+ return;
+ }
+ const int m_ParentItemID = mParentItem->data(0, Qt::UserRole).toInt();
+ if (newParentID != m_ParentItemID) {
+ mParentItem = mParentItem->parent();
+ }
+ MoveScriptCommand* command = new MoveScriptCommand(mpHost, this, mChildID, mOldParentID, newParentID, parentPosition, childPosition, mPrevParentPosition, mPrevChildPosition);
+ command->mpEditor = mpHost->mpEditorDialog;
+ command->mpHost = mpHost;
+ command->mParent = parent;
+ command->mStart = start;
+ command->mEnd = end;
+ command->mpParentItem = mParentItem;
+ command->mpPrevParentItem = mPrevParentItem;
+ command->mpItem = mdroppedItem;
+ if (mpUndoStack) {
+ mpUndoStack->push(command);
+ }
} else if (mIsActionTree) {
- mpHost->getActionUnit()->reParentAction(mChildID, mOldParentID, newParentID, parentPosition, childPosition);
- mpHost->getActionUnit()->updateToolbar();
+ if (mOldParentID == newParentID) {
+ return;
+ }
+ const int m_ParentItemID = mParentItem->data(0, Qt::UserRole).toInt();
Greptile
greptile
logic: Missing null check for mParentItem in the action tree case, unlike other tree types
suggested fix
if (!mParentItem) {
return;
}
const int m_ParentItemID = mParentItem->data(0, Qt::UserRole).toInt();
diff block
keyof TeamType,
(change?: ActivityChange, logItem?: ActivityLogItem) => ChangeMapping | null
> = {
+ api_token: (change) => {
+ const after = change?.after
+ if (after === undefined) {
+ return null
+ }
Greptile
greptile
logic: Missing null check for `change` parameter. Other handlers in this file check if change exists before accessing properties.
suggested fix
api_token: (change) => {
+ if (!change) {
return null
}
+ const after = change.after
if (after === undefined) {
return null
}
diff block
+import * as anchor from "@coral-xyz/anchor";
+import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes";
+import { PublicKey } from "@solana/web3.js";
+import { expect } from "chai";
+import WormholePostMessageShimIdl from "../idls/wormhole_post_message_shim.json";
+
+export async function getSequenceFromTx(
+ tx: string
+): Promise<{ emitter: PublicKey; sequence: bigint }> {
+ const txDetails = await getTransactionDetails(tx);
+
+ const borshEventCoder = new anchor.BorshEventCoder(
+ WormholePostMessageShimIdl as any
+ );
+
+ const innerInstructions = txDetails.meta.innerInstructions[0].instructions;
Greptile
greptile
logic: Missing null check for txDetails.meta.innerInstructions[0] could cause runtime errors if transaction has no inner instructions
suggested fix
+ if (!txDetails.meta?.innerInstructions?.[0]?.instructions) {
+ throw new Error('Transaction has no inner instructions');
+ }
const innerInstructions = txDetails.meta.innerInstructions[0].instructions;
diff block
rawValue: number,
dataIndex: number,
datasetIndex: number,
- datasetKey: string,
columnLabelFormats: NonNullable<BusterChartProps['columnLabelFormats']>,
chart: Chart,
hasMultipleShownDatasets: boolean,
percentageMode: undefined | 'stacked'
) => {
if (hasMultipleShownDatasets || percentageMode === 'stacked') {
- return getStackedPercentage(rawValue, dataIndex, datasetKey, columnLabelFormats, chart);
+ return getStackedPercentage(rawValue, dataIndex, datasetIndex, columnLabelFormats, chart);
}
- return getSeriesPercentage(rawValue, datasetIndex, datasetKey, columnLabelFormats, chart);
+ return getSeriesPercentage(rawValue, datasetIndex, columnLabelFormats, chart);
};
const getSeriesPercentage = (
rawValue: number,
datasetIndex: number,
- key: string,
columnLabelFormats: NonNullable<BusterChartProps['columnLabelFormats']>,
chart: Chart
): string => {
const total = chart.$totalizer.seriesTotals[datasetIndex];
const percentage = (rawValue / total) * 100;
Greptile
greptile
logic: Missing null check on total could lead to NaN percentages if seriesTotals[datasetIndex] is undefined
suggested fix
+ const total = chart.$totalizer.seriesTotals[datasetIndex] || 1;
+ const percentage = (rawValue / total) * 100;
diff block
+import { ApiEndpoint } from '@/types';
+
+export const getApiEndpoints = (data: any) => {
+ const endpoints: ApiEndpoint[] = [];
+
+ Object.keys(data).forEach(endpoint => {
+ const endpointData = data[endpoint];
+ const methods = Object.keys(endpointData);
+
+ methods.forEach(method => {
+ const methodData = endpointData[method];
+
+ if (!methodData.description.toLowerCase().includes('deprecated')) {
+ endpoints.push({
+ endpoint,
+ description: methodData.description,
+ method,
+ parameters: methodData.parameters,
+ body: methodData?.requestBody?.content['application/json']?.schema,
+ tags: methodData.tags,
Greptile
greptile
logic: Missing null check on methodData.tags could cause runtime error
suggested fix
+ tags: methodData?.tags,
diff block
+import { ApiEndpoint } from '@/types';
+
+export const getApiEndpoints = (data: any) => {
+ const endpoints: ApiEndpoint[] = [];
+
+ Object.keys(data).forEach(endpoint => {
+ const endpointData = data[endpoint];
+ const methods = Object.keys(endpointData);
+
+ methods.forEach(method => {
+ const methodData = endpointData[method];
+
+ if (!methodData.description.toLowerCase().includes('deprecated')) {
Greptile
greptile
logic: Missing null check on methodData.description could cause runtime error if description is undefined
suggested fix
+ if (methodData?.description?.toLowerCase()?.includes('deprecated') !== true) {
diff block
+import { useCodemirrorTheme } from '@/components/syntax-highlighter';
+import CodeMirror from '@uiw/react-codemirror';
+
+import { Badge, Txt } from '@mastra/playground-ui';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { useState, useMemo } from 'react';
+import clsx from 'clsx';
+
+import { typescriptLanguage } from '@codemirror/lang-javascript';
+import { useOpenApi } from './hooks/use-open-api';
+
+import { openApiToJs } from './utils/openapi-to-js';
+import { getApiEndpoints } from './utils/get-api-endpoints';
+import { ApiEndpoint } from '@/types';
+import { CopyIcon } from 'lucide-react';
+import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard';
+
+export interface ApiEndpointsProps {
+ agentId: string;
+}
+
+export const ApiEndpoints = ({ agentId }: ApiEndpointsProps) => {
+ const { data: openApi, isLoading } = useOpenApi();
+
+ const allEndpoints = getApiEndpoints(openApi?.paths || {});
+ if (isLoading) return <div>Loading...</div>;
+ if (!allEndpoints.length) return <div>No endpoints found</div>;
+
+ return <ApiEndpointsInner endspoints={allEndpoints} agentId={agentId} />;
+};
+
+const ApiEndpointsInner = ({ endspoints, agentId }: { endspoints: ApiEndpoint[]; agentId: string }) => {
+ const [selectedIndex, setSelectedIndex] = useState(0);
+
+ const onlyAgents = useMemo(
+ () =>
+ endspoints.filter(endpoint => endpoint.tags.includes('agents')).sort((a, b) => a.method.localeCompare(b.method)),
+ [endspoints],
+ );
+ const selectedEndpoint = onlyAgents[selectedIndex];
Greptile
greptile
logic: Missing null check for selectedEndpoint - could crash if onlyAgents is empty array
suggested fix
+ if (!onlyAgents.length) return <div>No agent endpoints found</div>;
const selectedEndpoint = onlyAgents[selectedIndex];
diff block
+/*
+ * This file is part of WebGoat, an Open Web Application Security Project utility. For details, please see http://www.owasp.org/
+ *
+ * Copyright (c) 2002 - 2019 Bruce Mayhew
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if
+ * not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Getting Source ==============
+ *
+ * Source for this application is maintained at https://github.com/WebGoat/WebGoat, a repository for free software projects.
+ */
+
+package org.owasp.webgoat.lessons.sqlinjection.advanced;
+
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.failed;
+import static org.owasp.webgoat.container.assignments.AttackResultBuilder.success;
+
+import java.io.IOException;
+import org.owasp.webgoat.container.assignments.AssignmentEndpoint;
+import org.owasp.webgoat.container.assignments.AttackResult;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * add a question: 1. Append new question to JSON string 2. add right solution to solutions array 3.
+ * add Request param with name of question to method head For a more detailed description how to
+ * implement the quiz go to the quiz.js file in webgoat-container -> js
+ */
+@RestController
+public class SqlInjectionQuiz implements AssignmentEndpoint {
+
+ String[] solutions = {"Solution 4", "Solution 3", "Solution 2", "Solution 3", "Solution 4"};
+ boolean[] guesses = new boolean[solutions.length];
+
+ @PostMapping("/SqlInjectionAdvanced/quiz")
+ @ResponseBody
+ public AttackResult completed(
+ @RequestParam String[] question_0_solution,
+ @RequestParam String[] question_1_solution,
+ @RequestParam String[] question_2_solution,
+ @RequestParam String[] question_3_solution,
+ @RequestParam String[] question_4_solution)
+ throws IOException {
+ int correctAnswers = 0;
+
+ String[] givenAnswers = {
+ question_0_solution[0],
+ question_1_solution[0],
+ question_2_solution[0],
+ question_3_solution[0],
+ question_4_solution[0]
+ };
Greptile
greptile
logic: Missing null checks on question parameters could cause NullPointerException if array is empty
suggested fix
String[] givenAnswers = {
+ question_0_solution != null && question_0_solution.length > 0 ? question_0_solution[0] : "",
+ question_1_solution != null && question_1_solution.length > 0 ? question_1_solution[0] : "",
+ question_2_solution != null && question_2_solution.length > 0 ? question_2_solution[0] : "",
+ question_3_solution != null && question_3_solution.length > 0 ? question_3_solution[0] : "",
+ question_4_solution != null && question_4_solution.length > 0 ? question_4_solution[0] : ""
};
diff block
import React, { useMemo } from 'react';
import { DatasetOption } from './useDatasetOptions';
+import { ChartType } from '@/api/asset_interfaces/metric';
export const useColors = ({
colors: colorsProp,
yAxisKeys,
y2AxisKeys,
+ selectedChartType,
datasetOptions
}: {
colors: string[];
yAxisKeys: string[];
y2AxisKeys: string[];
datasetOptions: DatasetOption[];
+ selectedChartType: ChartType;
}) => {
const numberOfYAxisKeys = yAxisKeys.length;
const numberOfY2AxisKeys = y2AxisKeys.length;
const totalNumberOfKeys = numberOfYAxisKeys + numberOfY2AxisKeys;
- const lastDatasetOption = datasetOptions[datasetOptions.length - 1];
- const sourceLength = lastDatasetOption.source.length || 1;
+ const sourceLength = datasetOptions[0].data.length;
Greptile
greptile
logic: Missing null check - will throw error if datasetOptions is empty array. Should add null check or fallback: `const sourceLength = datasetOptions[0]?.data?.length || 1;`
suggested fix
+ const sourceLength = datasetOptions[0]?.data?.length ?? 1;
diff block
+package tickettango;
+
+/**
+ * Represents an item in the shopping cart
+ */
+public class CartItem {
+ private Event event;
+ private int quantity;
+
+ /**
+ * Constructor for CartItem
+ *
+ * @param event The event for this cart item
+ * @param quantity Number of tickets
+ */
+ public CartItem(Event event, int quantity) {
+ this.event = event;
+ this.quantity = quantity;
+ }
Greptile
greptile
logic: Missing null check for event parameter and validation for negative quantity
suggested fix
public CartItem(Event event, int quantity) {
+ if (event == null) {
+ throw new IllegalArgumentException("Event cannot be null");
}
+ if (quantity < 0) {
+ throw new IllegalArgumentException("Quantity cannot be negative");
}
this.event = event;
this.quantity = quantity;
}
diff block
+package tickettango;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a user's order history
+ */
+public class OrderHistory {
+ private ArrayList<Order> orders;
+
+ /**
+ * Constructor for OrderHistory
+ */
+ public OrderHistory() {
+ this.orders = new ArrayList<>();
+ }
+
+ /**
+ * Add an order to the history
+ *
+ * @param order The order to add
+ */
+ public void addOrder(Order order) {
+ orders.add(order);
+ }
Greptile
greptile
logic: Missing null check before adding order to list
diff block
+package tickettango;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a user's order history
+ */
+public class OrderHistory {
+ private ArrayList<Order> orders;
+
+ /**
+ * Constructor for OrderHistory
+ */
+ public OrderHistory() {
+ this.orders = new ArrayList<>();
+ }
+
+ /**
+ * Add an order to the history
+ *
+ * @param order The order to add
+ */
+ public void addOrder(Order order) {
+ orders.add(order);
+ }
Greptile
greptile
logic: missing null check before adding order to list
suggested fix
public void addOrder(Order order) {
+ if (order != null) {
orders.add(order);
}
}
diff block
+package tickettango;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Represents an order in the TicketTango system
+ */
+public class Order {
+ private static int nextOrderId = 1000; // Starting order ID
+
+ private int orderId;
+ private String orderDate;
+ private ArrayList<CartItem> items;
+ private double subtotal;
+ private double tax;
+ private double total;
+ private String paymentMethod;
+
+ /**
+ * Constructor for Order
+ *
+ * @param cart The shopping cart
+ * @param paymentMethod The payment method used
+ */
+ public Order(Cart cart, String paymentMethod) {
Greptile
greptile
logic: missing null check for cart and paymentMethod parameters
diff block
</div>
))}
+ {/* Leverage message annotations to display tool calls */}
+ {messages.map(m => (
+ <div key={m.id} className="whitespace-pre-wrap">
+ {JSON.stringify(m.annotations)}
+ </div>
Greptile
greptile
logic: Missing null check for annotations - could cause rendering errors if m.annotations is undefined
diff block
return { dn, password };
};
- const issueCredentials: TRotationFactoryIssueCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
- callback
- ) => {
- const credentials = await $rotatePassword();
+ const issueCredentials: TRotationFactoryIssueCredentials<
+ TLdapPasswordRotationGeneratedCredentials,
+ TLdapPasswordRotationInput["temporaryParameters"]
+ > = async (callback, temporaryParameters) => {
+ const credentials = await $rotatePassword(
+ parameters.rotationMethod === LdapPasswordRotationMethod.TargetPrincipal
+ ? temporaryParameters?.password
+ : undefined
+ );
return callback(credentials);
};
const revokeCredentials: TRotationFactoryRevokeCredentials<TLdapPasswordRotationGeneratedCredentials> = async (
- _,
+ credentialsToRevoke,
callback
) => {
+ const currentPassword = credentialsToRevoke[activeIndex].password;
Greptile
greptile
logic: Missing null check on credentialsToRevoke[activeIndex]. Could throw if array is empty.
suggested fix
+ if (!credentialsToRevoke?.[activeIndex]?.password) {
+ throw new Error('No active credentials found to revoke');
+ }
const currentPassword = credentialsToRevoke[activeIndex].password;
diff block
};
};
+ const getSubscriberActiveCertBundle = async ({
+ subscriberName,
+ projectId,
+ actorId,
+ actorAuthMethod,
+ actor,
+ actorOrgId
+ }: TGetSubscriberActiveCertBundleDTO) => {
+ const subscriber = await pkiSubscriberDAL.findOne({
+ name: subscriberName,
+ projectId
+ });
Greptile
greptile
logic: Missing null check for subscriber after findOne() - could cause runtime error if subscriber not found ```suggestion const subscriber = await pkiSubscriberDAL.findOne({ name: subscriberName, projectId }); + if (!subscriber) throw new NotFoundError({ message: `PKI subscriber named '${subscriberName}' not found` }); ```
diff block
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Phoenix.MarketData.Core.Validation;
+using Phoenix.MarketData.Domain.Models;
+
+namespace Phoenix.MarketData.Application.Services.Decorators
+{
+ public class ValidationMarketDataServiceDecorator<T> : ServiceValidationDecorator<IMarketDataService<T>>, IMarketDataService<T>
+ where T : class, IMarketDataEntity
+ {
+ private readonly IValidator<T> _validator;
+
+ public ValidationMarketDataServiceDecorator(IMarketDataService<T> decorated, IValidator<T> validator)
+ : base(decorated)
+ {
+ _validator = validator ?? throw new ArgumentNullException(nameof(validator));
+ }
+
+ public async Task<string> PublishMarketDataAsync(T marketData)
Greptile
greptile
logic: Missing null check for marketData parameter before validation
suggested fix
public async Task<string> PublishMarketDataAsync(T marketData)
{
+ if (marketData == null)
{
+ throw new ArgumentNullException(nameof(marketData));
}
diff block
+using System;
+using System.Threading.Tasks;
+using Azure.Identity;
+using Azure.Security.KeyVault.Secrets;
+using Phoenix.MarketData.Core.Configuration;
+
+namespace Phoenix.MarketData.Infrastructure.Configuration
+{
+ public class MarketDataSecretProvider : IMarketDataSecretProvider
+ {
+ private readonly SecretClient _secretClient;
+ private readonly ISecretCache _secretCache;
+
+ public MarketDataSecretProvider(string keyVaultUrl, ISecretCache? secretCache = null)
+ {
+ if (string.IsNullOrEmpty(keyVaultUrl))
+ throw new ArgumentException("Key Vault URL cannot be null or empty", nameof(keyVaultUrl));
+
+ // Use DefaultAzureCredential for managed identity or local development
+ _secretClient = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
+ _secretCache = secretCache ?? new NoOpSecretCache();
+ }
+
+ public async Task<string> GetCosmosConnectionStringAsync()
+ {
+ return await GetSecretAsync("CosmosDbConnectionString");
+ }
+
+ public async Task<string> GetEventGridKeyAsync()
+ {
+ return await GetSecretAsync("EventGridKey");
+ }
+
+ public async Task<string> GetEventGridEndpointAsync()
+ {
+ return await GetSecretAsync("EventGridEndpoint");
+ }
+
+ public async Task<string> GetEventHubConnectionStringAsync()
+ {
+ return await GetSecretAsync("EventHubConnectionString");
+ }
+
+ private async Task<string> GetSecretAsync(string secretName)
Greptile
greptile
logic: Missing null check for secretName parameter
suggested fix
private async Task<string> GetSecretAsync(string secretName)
{
+ if (string.IsNullOrEmpty(secretName))
+ throw new ArgumentException("Secret name cannot be null or empty", nameof(secretName));
diff block
+using Phoenix.MarketData.Core.Models;
+using Phoenix.MarketData.Infrastructure.Validation;
+
+public class CryptoOrdinalSpotPriceDataValidator : IValidator<CryptoOrdinalSpotPriceData>
+{
+ public void Validate(CryptoOrdinalSpotPriceData data)
Greptile
greptile
logic: Missing null check for data parameter. Could throw NullReferenceException.
suggested fix
public void Validate(CryptoOrdinalSpotPriceData data)
{
+ if (data == null)
+ throw new ValidationException("Data cannot be null.");
diff block
+import { Color, Icon, Image, Keyboard, List, Toast, confirmAlert, showToast } from "@raycast/api";
+import { exec } from "child_process";
+import * as sudo from "sudo-prompt";
+
+export default class Disk {
+ number: number;
+ identifier: string;
+ size: string;
+ name: string;
+ details: string;
+ mountStatus: string;
+ type: string;
+ isWhole: boolean;
+ mountPoint: string | null;
+
+ constructor(number: number, type: string, identifier: string, name: string, size: string) {
+ this.number = number;
+ this.identifier = identifier;
+ this.name = name;
+ this.size = size;
+ this.type = type;
+ this.mountPoint = null;
+ this.details = "Initializing...";
+ this.mountStatus = "Initializing...";
+ this.isWhole = false;
+ }
+
+ getActions(postFunction: (type: "DiskRefresh" | "DiskUpdate") => void): {
+ title: string;
+ shortcut?: Keyboard.Shortcut;
+ icon: Image.ImageLike;
+ onAction: () => void;
+ }[] {
+ const action = (
+ title: string,
+ shortcut: Keyboard.Shortcut,
+ icon: Image.ImageLike,
+ method: "mount" | "unmount" | "eject" | "revealInFinder" | "showDetailCustomTerminal"
+ ) => ({
+ title,
+ shortcut,
+ icon,
+ onAction: () => {
+ this[method]().finally(
+ () =>
+ (method === "mount" || method === "unmount" || method === "eject") &&
+ postFunction(method === "eject" ? "DiskRefresh" : "DiskUpdate")
+ );
+ },
+ });
+
+ const failureAction = (title: string, icon?: Image.ImageLike, message?: string) => ({
+ title,
+ icon: Icon.Warning,
+ onAction: () =>
+ showToast({
+ style: Toast.Style.Failure,
+ title: `${this.identifier} ${title}`,
+ message,
+ }),
+ });
+
+ switch (this.mountStatus) {
+ case "Mounted":
+ return [
+ action("Unmount Volume", { modifiers: ["cmd"], key: "e" }, Icon.Eject, "unmount"),
+ action("Eject Full Drive", { modifiers: ["opt"], key: "e" }, Icon.Eject, "eject"),
+ action("Reveal in Finder", { modifiers: ["cmd"], key: "f" }, Icon.Eye, "revealInFinder"),
+ action("Terminal Info", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ ];
+ case "Unmounted":
+ return [
+ action("Mount Volume", { modifiers: ["cmd"], key: "e" }, Icon.ArrowDown, "mount"),
+ action("Eject Full Drive", { modifiers: ["opt"], key: "e" }, Icon.Eject, "eject"),
+ action("Terminal Info", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ ];
+ case "Whole":
+ return [
+ action("Unmount All Volumes", { modifiers: ["cmd"], key: "e" }, Icon.Eject, "unmount"),
+ action("Mount All Volumes", { modifiers: ["cmd", "shift"], key: "e" }, Icon.ArrowDown, "mount"),
+ action("Eject Drive", { modifiers: ["opt"], key: "e" }, Icon.Eject, "eject"),
+ action("Terminal Info", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ ];
+ case "Unmountable":
+ return [
+ action("Eject", { modifiers: ["opt"], key: "e" }, Icon.Eject, "eject"),
+ action("Terminal Info", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ failureAction("Unmountable"),
+ ];
+ case "Container":
+ return [
+ action("Eject All Volumes", { modifiers: ["opt"], key: "e" }, Icon.Eject, "eject"),
+ action("Terminal Info", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ ];
+ case "Timed Out":
+ return [
+ action("Unmount Disk", { modifiers: ["cmd"], key: "e" }, Icon.Eject, "unmount"),
+ action("Mount Disk", { modifiers: ["cmd", "shift"], key: "e" }, Icon.ArrowDown, "mount"),
+ action("Eject Disk", { modifiers: ["opt"], key: "e" }, Icon.Eject, "eject"),
+ action("Info in Custom Terminal", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ ];
+ default:
+ return [
+ failureAction("Mountability Unknown", "Shouldn't happen. Try reloading or so"),
+ action("Unmount Disk", { modifiers: ["cmd"], key: "e" }, Icon.Eject, "unmount"),
+ action("Mount Disk", { modifiers: ["cmd", "shift"], key: "e" }, Icon.ArrowDown, "mount"),
+ action("Terminal Info", { modifiers: ["cmd"], key: "t" }, Icon.Info, "showDetailCustomTerminal"),
+ ];
+ }
+ }
+
+ async showDetailCustomTerminal() {
+ const command = `diskutil info ${this.identifier}`;
+
+ // Execute AppleScript to open a new Terminal window and run the command
+ const fullCommand = `
+ osascript -e 'tell application "Terminal"
+ activate
+ do script "${command}"
+ delay 1000
+ set frontmost of the first window to true
+ end tell'
+ `;
+
+ showToast({
+ style: Toast.Style.Animated,
+ title: `Opening new terminal...`,
+ });
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // delay
+ showToast({
+ style: Toast.Style.Success,
+ title: `Opened new terminal`,
+ });
+ try {
+ await Disk.execCommand(fullCommand);
+ } catch (error) {
+ showToast({
+ style: Toast.Style.Failure,
+ title: "Failed to open terminal",
+ message: String(error),
+ });
+ }
+ }
+
+ async revealInFinder() {
+ try {
+ await Disk.execCommand(`open "${this.mountPoint}"`);
Greptile
greptile
logic: Missing null check before using mountPoint. If mountPoint is null, this will try to open "null" as a path. ```suggestion + if (!this.mountPoint) { + throw new Error("No mount point available"); } await Disk.execCommand(`open "${this.mountPoint}"`); ```
diff block
private async emitWorkflowRunUpdatedEvent({
workflowRunBefore,
updatedFields,
- diff,
}: {
workflowRunBefore: WorkflowRunWorkspaceEntity;
updatedFields: string[];
- diff: object;
}) {
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
Greptile
greptile
logic: Missing null check on create() call which could throw if context is invalid
suggested fix
+ const workspaceId = this.scopedWorkspaceContextFactory.create()?.workspaceId;
diff block
+import { showToast, Toast } from '@raycast/api';
+import convert from 'xml-js';
+import { BggSearchResponse, BggDetailsResponse, BoardGameXml, GameDetailsXml } from './models';
+
+export async function parseResults(response: Response): Promise<BggSearchResponse> {
+ const resultsArr: BggSearchResponse = [];
+
+ try {
+ const xml = await response.text();
+ const obj = convert.xml2js(xml) as BoardGameXml;
+
+ const elements = obj.elements?.[0]?.elements || [];
+
+ elements.forEach((el) => {
+ const title = el.elements.find((e) => e.name === 'name');
+
+ if (!title) return;
+
+ resultsArr.push({
+ bggId: el.attributes.id,
+ title: title.attributes.value,
+ url: `https://boardgamegeek.com/boardgame/${el.attributes.id}`,
+ });
+ });
+ } catch (error) {
+ console.error(error);
+ showToast(Toast.Style.Failure, 'Could not parse response');
+ }
+
+ return resultsArr;
+}
+
+export async function parseGameData(response: Response): Promise<BggDetailsResponse> {
+ const gameData: BggDetailsResponse = {};
+
+ try {
+ const xml = await response.text();
+
+ const result = convert.xml2js(xml, { compact: true }) as GameDetailsXml;
+
+ gameData.bggId = result?.items?.item?._attributes?.objectid;
+ gameData.title = result?.items?.item.name?._text;
+ gameData.img = result?.items?.item?.thumbnail?._text;
+ gameData.description = result?.items?.item?.description?._text;
+ gameData.minPlayers = parseInt(result?.items?.item?.minplayers?._attributes?.value);
+ gameData.maxPlayers = parseInt(result?.items?.item?.maxplayers?._attributes?.value);
+ gameData.avgPlaytime = parseInt(result?.items?.item?.playingtime?._attributes?.value);
Greptile
greptile
logic: Missing null checks on parseInt calls - could return NaN if value is undefined
suggested fix
gameData.bggId = result?.items?.item?._attributes?.objectid;
gameData.title = result?.items?.item.name?._text;
gameData.img = result?.items?.item?.thumbnail?._text;
gameData.description = result?.items?.item?.description?._text;
+ gameData.minPlayers = result?.items?.item?.minplayers?._attributes?.value ? parseInt(result.items.item.minplayers._attributes.value) : undefined;
+ gameData.maxPlayers = result?.items?.item?.maxplayers?._attributes?.value ? parseInt(result.items.item.maxplayers._attributes.value) : undefined;
+ gameData.avgPlaytime = result?.items?.item?.playingtime?._attributes?.value ? parseInt(result.items.item.playingtime._attributes.value) : undefined;
diff block
+import { useDebounceFn, useMemoizedFn } from 'ahooks';
+import type { IBusterMetric } from '../interfaces';
+import {
+ BusterMetric,
+ DEFAULT_CHART_CONFIG,
+ IBusterMetricChartConfig,
+ ShareRole,
+ VerificationStatus
+} from '@/api/asset_interfaces';
+import { prepareMetricUpdateMetric } from '../helpers';
+import { MetricUpdateMetric } from '@/api/buster_socket/metrics';
+import { ColumnSettings, IColumnLabelFormat } from '@/components/charts';
+import { useBusterWebSocket } from '../../BusterWebSocket';
+
+export const useUpdateMetricConfig = ({
+ getMetricId,
+ getMetricMemoized,
+ setMetrics,
+ startTransition,
+ onInitializeMetric
+}: {
+ getMetricMemoized: ({ metricId }: { metricId?: string }) => IBusterMetric;
+ onInitializeMetric: (metric: BusterMetric) => void;
+ getMetricId: (metricId?: string) => string;
+ setMetrics: (metrics: Record<string, IBusterMetric>) => void;
+ startTransition: (fn: () => void) => void;
+}) => {
+ const busterSocket = useBusterWebSocket();
+
+ const onUpdateMetric = useMemoizedFn(
+ async (newMetricPartial: Partial<IBusterMetric>, saveToServer: boolean = true) => {
+ const metricId = getMetricId(newMetricPartial.id);
+ const currentMetric = getMetricMemoized({ metricId })!;
+ const newMetric: IBusterMetric = {
+ ...currentMetric,
+ ...newMetricPartial
+ };
+ setMetrics({
+ [metricId]: newMetric
+ });
+
+ //This will trigger a rerender and push prepareMetricUpdateMetric off UI metric
+ startTransition(() => {
+ const isReadyOnly = currentMetric.permission === ShareRole.VIEWER;
+ if (saveToServer && !isReadyOnly) {
+ _prepareMetricAndSaveToServer(newMetric, currentMetric);
+ }
+ });
+
+ return newMetric;
+ }
+ );
+
+ const _CheckUpdateMetric = useMemoizedFn((metric: BusterMetric) => {
+ const draftSessionId = metric.draft_session_id;
+ const currentMessage = getMetricMemoized({ metricId: metric.id });
+ if (draftSessionId && !currentMessage?.draft_session_id) {
+ onUpdateMetric({
+ id: metric.id,
+ draft_session_id: draftSessionId
+ });
+ }
+ return metric;
+ });
+
+ const updateMetricToServer = useMemoizedFn((payload: MetricUpdateMetric['payload']) => {
+ return busterSocket.emitAndOnce({
+ emitEvent: {
+ route: '/metrics/update',
+ payload
+ },
+ responseEvent: {
+ route: '/metrics/update:updateMetricState',
+ callback: _CheckUpdateMetric
+ }
+ });
+ });
+
+ const { run: _updateMetricToServer } = useDebounceFn(updateMetricToServer, {
+ wait: 300
+ });
+
+ const { run: _prepareMetricAndSaveToServer } = useDebounceFn(
+ useMemoizedFn((newMetric: IBusterMetric, oldMetric: IBusterMetric) => {
+ const changedValues = prepareMetricUpdateMetric(newMetric, oldMetric);
+ if (changedValues) {
+ _updateMetricToServer(changedValues);
+ }
+ }),
+ { wait: 700 }
+ );
+
+ const onUpdateMetricChartConfig = useMemoizedFn(
+ ({
+ metricId,
+ chartConfig,
+ ignoreUndoRedo
+ }: {
+ metricId?: string;
+ chartConfig: Partial<IBusterMetricChartConfig>;
+ ignoreUndoRedo?: boolean;
+ }) => {
+ const currentMetric = getMetricMemoized({
+ metricId
+ });
+
+ if (!ignoreUndoRedo) {
+ // undoRedoParams.addToUndoStack({
+ // metricId: editMetric.id,
+ // messageId: editMessage.id,
+ // chartConfig: editMessage.chart_config
+ // });
+ }
+
+ const newChartConfig: IBusterMetricChartConfig = {
+ ...DEFAULT_CHART_CONFIG,
+ ...currentMetric.chart_config,
+ ...chartConfig
+ };
+ onUpdateMetric({
+ id: metricId,
+ chart_config: newChartConfig
+ });
+ }
+ );
+
+ const onUpdateColumnLabelFormat = useMemoizedFn(
+ ({
+ columnId,
+ columnLabelFormat,
+ metricId
+ }: {
+ columnId: string;
+ metricId?: string;
+ columnLabelFormat: Partial<IColumnLabelFormat>;
+ }) => {
+ const currentMetric = getMetricMemoized({ metricId });
Greptile
greptile
logic: missing null check for currentMetric and its chart_config property could cause runtime errors ```suggestion const currentMetric = getMetricMemoized({ metricId }); + if (!currentMetric?.chart_config) { + throw new Error(`Invalid metric or chart config for id: ${metricId}`); } ```
diff block
);
const soccerArticles = soccerArticlesData?.articles || [];
- const soccerItems = soccerArticles.map((soccerArticle, index) => {
- const articleDate = new Date(soccerArticle.published).toLocaleDateString([], {
+ const soccerItems = soccerArticles?.map((soccerArticle, index) => {
+ const articleDate = new Date(soccerArticle?.published).toLocaleDateString([], {
day: "2-digit",
month: "short",
year: "numeric",
});
const accessoryTitle = articleDate;
const accessoryToolTip = "Date Published";
+ let articleType = soccerArticle?.type;
+
+ if (articleType === "HeadlineNews") {
+ articleType = "Headline";
+ }
return (
<List.Item
key={index}
- title={`${soccerArticle.headline}`}
+ title={`${soccerArticle?.headline}`}
icon={
- soccerArticle.images && soccerArticle.images[0]?.url ? { source: soccerArticle.images[0].url } : undefined
+ soccerArticle?.images && soccerArticle?.images[0]?.url ? { source: soccerArticle?.images[0]?.url } : undefined
}
- accessories={[{ text: { value: `${accessoryTitle}` }, tooltip: accessoryToolTip }]}
+ accessories={[
+ { tag: { value: articleType, color: Color.Green }, icon: Icon.Megaphone },
Greptile
greptile
logic: Missing null check for articleType in tag value could cause undefined to display
diff block
+import { ProductManifest } from '../../frontend/src/types'
+
+export const manifest: ProductManifest = {
+ name: 'Messaging',
+ scenes: {
+ MessagingBroadcasts: {
+ import: () => import('./frontend/Broadcasts'),
+ name: 'Messaging',
+ projectBased: true,
+ },
+ MessagingProviders: {
+ import: () => import('./frontend/Providers'),
+ name: 'Messaging',
+ projectBased: true,
+ },
+ },
+ routes: {
+ // URL: [Scene, SceneKey]
+ '/messaging/providers': ['MessagingProviders', 'messagingProviders'],
+ '/messaging/providers/:id': ['MessagingProviders', 'messagingProvider'],
+ '/messaging/providers/new': ['MessagingProviders', 'messagingProviderNew'],
+ '/messaging/providers/new/*': ['MessagingProviders', 'messagingProviderNew'],
+ '/messaging/broadcasts': ['MessagingBroadcasts', 'messagingBroadcasts'],
+ '/messaging/broadcasts/:id': ['MessagingBroadcasts', 'messagingBroadcast'],
+ '/messaging/broadcasts/new': ['MessagingBroadcasts', 'messagingBroadcastNew'],
+ },
+ redirects: {
+ '/messaging': '/messaging/broadcasts',
+ },
+ urls: {
+ messagingBroadcasts: (): string => '/messaging/broadcasts',
+ messagingBroadcast: (id?: string): string => `/messaging/broadcasts/${id}`,
Greptile
greptile
style: Missing null check for undefined id could result in URL with 'undefined' string. Consider adding a default value or validation. ```suggestion + messagingBroadcast: (id?: string): string => `/messaging/broadcasts/${id ?? ''}` ```
diff block
+// This file is a fallback for using MaterialIcons on Android and web.
+
+import MaterialIcons from '@expo/vector-icons/MaterialIcons';
+import { SymbolWeight } from 'expo-symbols';
+import React from 'react';
+import { OpaqueColorValue, StyleProp, ViewStyle } from 'react-native';
+
+// Add your SFSymbol to MaterialIcons mappings here.
+const MAPPING = {
+ // See MaterialIcons here: https://icons.expo.fyi
+ // See SF Symbols in the SF Symbols app on Mac.
+ 'house.fill': 'home',
+ 'paperplane.fill': 'send',
+ 'chevron.left.forwardslash.chevron.right': 'code',
+ 'chevron.right': 'chevron-right',
+} as Partial<
+ Record<
+ import('expo-symbols').SymbolViewProps['name'],
+ React.ComponentProps<typeof MaterialIcons>['name']
+ >
+>;
+
+export type IconSymbolName = keyof typeof MAPPING;
+
+/**
+ * An icon component that uses native SFSymbols on iOS, and MaterialIcons on Android and web. This ensures a consistent look across platforms, and optimal resource usage.
+ *
+ * Icon `name`s are based on SFSymbols and require manual mapping to MaterialIcons.
+ */
+export function IconSymbol({
+ name,
+ size = 24,
+ color,
+ style,
+}: {
+ name: IconSymbolName;
+ size?: number;
+ color: string | OpaqueColorValue;
+ style?: StyleProp<ViewStyle>;
+ weight?: SymbolWeight;
+}) {
+ return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
Greptile
greptile
logic: missing null check for MAPPING[name] - could crash if name not found in mapping
diff block
validated_data["sync_frequency_interval"] = sync_frequency_interval
instance.sync_frequency_interval = sync_frequency_interval
+ if sync_time_of_day:
+ if sync_time_of_day != instance.sync_time_of_day:
+ was_sync_time_of_day_updated = True
+ validated_data["sync_time_of_day"] = sync_time_of_day
+ instance.sync_time_of_day = sync_time_of_day
Greptile
greptile
logic: Missing null check here. If sync_time_of_day is None, this could lead to unexpected behavior when a user clears the field.
suggested fix
+ if sync_time_of_day is not None:
if sync_time_of_day != instance.sync_time_of_day:
was_sync_time_of_day_updated = True
validated_data["sync_time_of_day"] = sync_time_of_day
instance.sync_time_of_day = sync_time_of_day
diff block
import { IRole } from '@app/interfaces';
-import {
- createRoleAPI,
- deleteRoleAPI,
- getRolesAPI,
- updateRoleAPI
-} from '@app/services/client/api';
+import { createRoleAPI, deleteRoleAPI, getRolesAPI, updateRoleAPI } from '@app/services/client/api';
import { rolesState } from '@app/stores/';
import { useCallback } from 'react';
import { useAtom } from 'jotai';
import { useQuery } from '../useQuery';
-import cloneDeep from 'lodash/cloneDeep';
+import { useFirstLoad } from '../useFirstLoad';
export const useRoles = () => {
- const [roles, setRoles] = useAtom(rolesState);
+ const [roles, setRoles] = useAtom(rolesState);
+ const { firstLoadData: firstRolesLoad } = useFirstLoad();
+ const { loading, queryCall: getRolesQueryCall } = useQuery(getRolesAPI);
+ const { loading: createRoleLoading, queryCall: createRoleQueryCall } = useQuery(createRoleAPI);
+ const { loading: updateRoleLoading, queryCall: updateRoleQueryCall } = useQuery(updateRoleAPI);
+ const { loading: deleteRoleLoading, queryCall: deleteRoleQueryCall } = useQuery(deleteRoleAPI);
- const { loading, queryCall: getRolesQueryCall } = useQuery(getRolesAPI);
- const {
- loading: createRoleLoading,
- queryCall: createRoleQueryCall
- } = useQuery(createRoleAPI);
- const {
- loading: updateRoleLoading,
- queryCall: updateRoleQueryCall
- } = useQuery(updateRoleAPI);
- const {
- loading: deleteRoleLoading,
- queryCall: deleteRoleQueryCall
- } = useQuery(deleteRoleAPI);
+ const getRoles = useCallback(async () => {
+ try {
+ const res = await getRolesQueryCall();
- const getRoles = useCallback(() => {
- getRolesQueryCall().then((response) => {
- if (response.data.items.length) {
- setRoles(response.data.items);
- }
- });
- }, [getRolesQueryCall, setRoles]);
+ return res;
+ } catch (error) {
+ console.error('Failed to get roles', error);
+ }
+ }, [getRolesQueryCall]);
- const createRole = useCallback(
- async (role: IRole) => {
- createRoleQueryCall(role).then((response) => {
- setRoles([response.data, ...roles]);
- });
- },
- [roles, createRoleQueryCall, setRoles]
- );
+ const createRole = useCallback(
+ async (role: IRole) => {
+ try {
+ const res = await createRoleQueryCall(role);
- const updateRole = useCallback(
- async (role: IRole) => {
- updateRoleQueryCall(role).then(() => {
- const index = roles.findIndex((item) => item.id === role.id);
- const tempRoles = cloneDeep(roles);
- if (index >= 0) {
- tempRoles[index].name = role.name;
- }
+ return res;
+ } catch (error) {
+ console.error('Failed to create role', error);
+ }
+ },
+ [createRoleQueryCall]
+ );
- setRoles(tempRoles);
- });
- },
- [roles, setRoles, updateRoleQueryCall]
- );
+ const updateRole = useCallback(
+ async (role: IRole) => {
+ try {
+ const res = await updateRoleQueryCall(role);
+ return res;
+ } catch (error) {
+ console.error('Failed to update role', error);
+ }
+ },
+ [updateRoleQueryCall]
+ );
- const deleteRole = useCallback(
- async (id: string) => {
- deleteRoleQueryCall(id).then(() => {
- setRoles(roles.filter((role) => role.id !== id));
- });
- },
- [deleteRoleQueryCall, setRoles, roles]
- );
+ const deleteRole = useCallback(
+ async (id: string) => {
+ try {
+ const res = await deleteRoleQueryCall(id);
+ return res;
+ } catch (error) {
+ console.error('Failed to delete role:', error);
+ }
+ },
+ [deleteRoleQueryCall]
+ );
- return {
- roles,
- loading,
- getRoles,
+ const loadRoles = useCallback(async () => {
+ try {
+ const res = await getRoles();
- createRole,
- createRoleLoading,
+ if (res) {
+ setRoles(res.data.items);
+ return;
+ } else {
Greptile
greptile
logic: Missing null check on res.data and res.data.items before accessing. Could throw runtime error if API returns unexpected response structure.
suggested fix
+ if (res?.data?.items) {
setRoles(res.data.items);
return;
} else {
diff block
+import { getPreferenceValues } from "@raycast/api";
+import axios from "axios";
+
+interface Preferences {
+ username: string;
+ githubToken?: string;
+}
+
+export interface GitHubUser {
+ login: string;
+ name: string;
+ avatar_url: string;
+ html_url: string;
+ bio: string;
+ public_repos: number;
+ followers: number;
+ following: number;
+ created_at: string;
+ location?: string;
+ blog?: string;
+ company?: string;
+ twitter_username?: string;
+}
+
+export interface GitHubRepository {
+ id: number;
+ name: string;
+ html_url: string;
+ description: string;
+ stargazers_count: number;
+ forks_count: number;
+ language: string;
+ updated_at: string;
+}
+
+export interface ContributionDay {
+ date: string;
+ count: number;
+ level: number;
+}
+
+export interface ContributionsCollection {
+ contributionCalendar: {
+ totalContributions: number;
+ weeks: Array<{
+ contributionDays: ContributionDay[];
+ }>;
+ };
+}
+
+const getUsername = (): string => {
+ const preferences = getPreferenceValues<Preferences>();
+ return preferences.username;
+};
+
+export const fetchUserProfile = async (): Promise<GitHubUser> => {
+ const username = getUsername();
+ const preferences = getPreferenceValues<Preferences>();
+ const headers: Record<string, string> = {};
+
+ if (preferences.githubToken) {
+ headers["Authorization"] = `token ${preferences.githubToken}`;
+ }
+
+ try {
+ const response = await axios.get(`https://api.github.com/users/${username}`, { headers });
+ return response.data;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response?.status === 403) {
+ const rateLimitMessage = "GitHub API rate limit exceeded. ";
+ const authMessage = preferences.githubToken
+ ? "Please check your token's validity."
+ : "Please add a GitHub personal access token in extension preferences to increase the rate limit.";
+
+ throw new Error(rateLimitMessage + authMessage);
+ }
+ throw error;
+ }
+};
+
+export const fetchUserRepositories = async (): Promise<GitHubRepository[]> => {
+ const username = getUsername();
+ const preferences = getPreferenceValues<Preferences>();
+ const headers: Record<string, string> = {};
+
+ if (preferences.githubToken) {
+ headers["Authorization"] = `token ${preferences.githubToken}`;
+ }
+
+ try {
+ const response = await axios.get(`https://api.github.com/users/${username}/repos?sort=updated&per_page=10`, {
+ headers,
+ });
+ return response.data;
+ } catch (error) {
+ if (axios.isAxiosError(error) && error.response?.status === 403) {
+ const rateLimitMessage = "GitHub API rate limit exceeded. ";
+ const authMessage = preferences.githubToken
+ ? "Please check your token's validity."
+ : "Please add a GitHub personal access token in extension preferences to increase the rate limit.";
+
+ throw new Error(rateLimitMessage + authMessage);
+ }
+ throw error;
+ }
+};
+
+export const fetchContributionData = async (): Promise<ContributionsCollection> => {
+ const username = getUsername();
+ const preferences = getPreferenceValues<Preferences>();
+
+ if (!preferences.githubToken) {
+ // If no token provided, return mock data
+ return generateMockContributionData();
+ }
+
+ try {
+ const query = `
+ query {
+ user(login: "${username}") {
+ contributionsCollection {
+ contributionCalendar {
+ totalContributions
+ weeks {
+ contributionDays {
+ date
+ contributionCount
+ color
+ }
+ }
+ }
+ }
+ }
+ }
+ `;
+
+ const response = await axios.post(
+ "https://api.github.com/graphql",
+ { query },
+ {
+ headers: {
+ Authorization: `bearer ${preferences.githubToken}`,
+ "Content-Type": "application/json",
+ },
+ },
+ );
+
+ const calendarData = response.data.data.user.contributionsCollection.contributionCalendar;
Greptile
greptile
logic: Missing null check for response.data.data.user - could throw if user not found
suggested fix
+ const calendarData = response.data?.data?.user?.contributionsCollection?.contributionCalendar;
diff block
}
};
-export const getRequestMessages = (request: any): Message[] => {
- return (
- request.messages?.map((msg: any) => {
- // Handle function calls first
- if (msg.function_call || msg.tool_calls) {
- const toolCallMsg = handleToolCalls(msg);
- return {
- ...toolCallMsg,
- content: toolCallMsg.content || "",
- _type: "functionCall",
- };
- }
+const processFunctionCall = (msg: any): Message => {
+ const toolCallMsg = handleToolCalls(msg);
+ return {
+ ...toolCallMsg,
+ content: toolCallMsg.content || "",
+ _type: "functionCall",
+ };
+};
- // Handle tool responses
- if (msg.role === "tool" || msg.role === "function") {
- const toolResponseMsg = handleToolResponse(msg);
+const processToolResponse = (msg: any): Message => {
+ const toolResponseMsg = handleToolResponse(msg);
+ return {
+ ...toolResponseMsg,
+ content: toolResponseMsg.content || "",
+ _type: "function",
+ name: msg.name,
+ tool_calls: [
+ {
+ name: msg.name,
+ arguments: {
+ query_result: msg.content,
+ },
+ },
+ ],
+ };
+};
+
+const processArrayContent = (msg: any): Message => {
+ // Process each item in the array uniformly
+ return {
+ contentArray: msg.content.map((item: any) => {
+ if (item.type === "text") {
return {
- ...toolResponseMsg,
- content: toolResponseMsg.content || "",
- _type: "function",
- name: msg.name,
- tool_calls: [
- {
- name: msg.name,
- arguments: {
- query_result: msg.content,
- },
- },
- ],
+ content: item.text || "",
+ role: msg.role || "user",
+ _type: "message",
};
- }
-
- // Handle array content (e.g. text + image)
- if (Array.isArray(msg.content)) {
- const textContent = msg.content.find(
- (item: any) => item.type === "text"
- );
- const imageContent = msg.content.find(
- (item: any) => item.type === "image_url"
- );
-
- if (textContent && imageContent) {
- return {
- role: msg.role,
- _type: "image",
- content: textContent.text || "",
- image_url: imageContent.image_url.url,
- };
- }
- }
-
- // Handle single image content
- if (msg.content?.type === "image_url") {
+ } else if (item.type === "image_url") {
return {
- role: msg.role,
+ role: msg.role || "user",
_type: "image",
- content: msg.content.text || "",
- image_url: msg.content.image_url.url,
+ content: "",
+ image_url: item.image_url.url,
};
}
-
- // Handle regular messages
return {
- content: getFormattedMessageContent(msg.content) || "",
+ content: JSON.stringify(item),
role: msg.role || "user",
- _type: getContentType(msg as any),
+ _type: "message",
};
- }) ?? []
- );
+ }),
+ _type: "contentArray",
+ role: msg.role,
+ };
+};
+
+const processSingleImage = (msg: any): Message => {
+ if (typeof msg.content.image_url === "string") {
+ return {
+ role: msg.role,
+ _type: "image",
+ content: msg.content.text || "",
+ image_url: msg.content.image_url,
+ };
Greptile
greptile
logic: Missing null check for msg.content.image_url before accessing it as a string
suggested fix
+ if (msg.content?.image_url && typeof msg.content.image_url === "string") {
return {
role: msg.role,
_type: "image",
content: msg.content.text || "",
image_url: msg.content.image_url,
};
diff block
+import { Color, Icon, List } from "@raycast/api";
+import { useTripSearch } from "../api/client";
+import { ArrivalOrDeparture, Trip } from "../api/types";
+import Accessory = List.Item.Accessory;
+
+export interface TripsProps {
+ fromStation: string;
+ toStation: string;
+ date: string;
+ searchArrival: boolean;
+}
+
+const formatter = Intl.DateTimeFormat("en-NL", {
+ timeStyle: "short",
+ timeZone: "Europe/Amsterdam",
+});
+
+export function Trips(props: TripsProps) {
+ const { isLoading, data } = useTripSearch(props.fromStation, props.toStation, props.date, props.searchArrival);
+
+ return (
+ <List isLoading={isLoading} navigationTitle="Search Trips">
+ {((data === undefined ? { payload: [] } : data).trips || []).map(renderTripRow)}
+ </List>
+ );
+}
+
+function getTripTime(trip: Trip): [string, string] {
+ if (trip.legs.length === 1) {
+ const start = new Date(Date.parse((trip.legs[0].origin.actualDateTime ?? trip.legs[0].origin.plannedDateTime)!));
+ const end = new Date(
+ Date.parse((trip.legs[0].destination.actualDateTime ?? trip.legs[0].destination.plannedDateTime)!),
+ );
+ return [formatter.format(start), formatter.format(end)];
+ }
+
+ const start = new Date(Date.parse((trip.legs[0].origin.actualDateTime ?? trip.legs[0].origin.plannedDateTime)!));
+ const end = new Date(
+ Date.parse(
+ (trip.legs[trip.legs.length - 1].destination.actualDateTime ??
+ trip.legs[trip.legs.length - 1].destination.plannedDateTime)!,
+ ),
+ );
+ return [formatter.format(start), formatter.format(end)];
+}
+
+function renderTripRow(trip: Trip) {
+ const fromStationName: string | undefined = trip.legs[0].origin.name;
Greptile
greptile
logic: Missing null check for trip.legs[0] - could throw if trip.legs is empty array
suggested fix
+ const fromStationName: string | undefined = trip.legs.length > 0 ? trip.legs[0].origin.name : undefined;
diff block
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import App from './app.tsx'
+import { posthog } from 'posthog-js'
+
+posthog.init(import.meta.env.VITE_POSTHOG_KEY || '', {
+ api_host: import.meta.env.VITE_POSTHOG_HOST || 'http://localhost:8010',
+ autocapture: false,
+})
+
+createRoot(document.getElementById('root')!).render(
Greptile
greptile
logic: Missing null check before non-null assertion (!). getElementById('root') could return null
suggested fix
+const rootElement = document.getElementById('root');
+if (!rootElement) throw new Error('Root element not found');
+createRoot(rootElement).render(