Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add CLICKHOUSE_MAX_EXECUTION_TIME, update fetchData #264

Merged
merged 1 commit into from
Jun 3, 2024
Merged

Conversation

duyet
Copy link
Owner

@duyet duyet commented Jun 3, 2024

No description provided.

Copy link

vercel bot commented Jun 3, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
clickhouse-monitoring ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 3, 2024 10:08am

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @duyet - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 11 issues found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 4 issues found

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

<TopRightToolbarExtras database={database} table={table} />
}
config={config}
data={columns}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Potential issue with data structure

Ensure that columns is structured correctly for the DataTable component. The change from fetchDataWithCache() to destructuring might have altered the expected data format.

query: sql,
format: 'JSONEachRow',
clickhouse_settings: { use_query_cache: 1, query_cache_ttl: 120 },
})

if (!data || !data.length || !data?.[0]?.['count()']) return null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Redundant null check

The null check !data is redundant since data is destructured from the response and will always be defined. Consider removing it.

Suggested change
if (!data || !data.length || !data?.[0]?.['count()']) return null
if (!data.length || !data?.[0]?.['count()']) return null

}: {
web?: B
clickhouse_settings?: ClickHouseSettings
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Missing documentation for new parameter

Consider adding a comment or documentation for the new clickhouse_settings parameter to explain its purpose and usage.

Suggested change
clickhouse_settings?: ClickHouseSettings
// Optional settings for configuring the ClickHouse client
clickhouse_settings?: ClickHouseSettings

@@ -10,14 +10,16 @@ import { seeding } from './seeding'
export const getCustomDashboards = async () => {
await seeding()

const dashboards = await fetchData<TableChartsRow[]>({
const q1 = fetchData<TableChartsRow[]>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Potential performance improvement

Consider using Promise.all directly without intermediate variables q1 and q2 to potentially improve readability and performance.

Suggested change
const q1 = fetchData<TableChartsRow[]>({
await Promise.all([
fetchData<TableChartsRow[]>({
query: `SELECT * FROM ${TABLE_CHARTS} FINAL ORDER BY ordering ASC`,
})
]);

databases = await fetchDataWithCache()({
query: listDatabases,
})
const { data: databases }: { data: DatabaseCount[] } =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Type annotation redundancy

The type annotation { data: DatabaseCount[] } is redundant since TypeScript can infer the type from the function return type. Consider removing it for cleaner code.

Suggested change
const { data: databases }: { data: DatabaseCount[] } =
const { data: databases } =

try {
data = await fetchData<{ 'count()': string }[]>({
const { data } = await fetchData<{ 'count()': string }[]>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider reverting to a structure similar to the original while incorporating the necessary changes.

The new code introduces more complexity compared to the previous version. Here are some points to consider:

  1. Inline Changes: The new code has inline changes within the try block, making it harder to read and understand. The previous version separated data fetching and data validation logic, which was easier to follow.

  2. Error Handling: While the error handling is now at the end of the function, the inline changes make it less clear where potential points of failure are.

  3. Readability: The previous version had a clear separation of concerns—fetching data, checking data, and rendering the component. The new version mixes these steps together, reducing readability.

  4. Maintainability: The previous version was more modular, making it easier to maintain and update individual parts of the function. The new version's inline approach makes it harder to isolate and update specific logic.

Consider reverting to a structure similar to the original while incorporating the necessary changes. Here's a revised version:

export async function CountBadge({
  sql,
  className,
  variant = 'outline',
}: CountBadgeProps): Promise<JSX.Element | null> {
  if (!sql) return null;

  let data: { 'count()': string }[] = [];

  try {
    const response = await fetchData<{ 'count()': string }[]>({
      query: sql,
      format: 'JSONEachRow',
      clickhouse_settings: { use_query_cache: 1, query_cache_ttl: 120 },
    });
    data = response.data;
  } catch (e: any) {
    console.error(
      `<CountBadge />: could not get count for sql: ${sql}, error: ${e}`
    );
    return null;
  }

  if (!data || !data.length || !data?.[0]?.['count()']) return null;

  const count = data[0]['count()'] || 0;
  if (count == 0) return null;

  return (
    <Badge className={className} variant={variant}>
      {count}
    </Badge>
  );
}

This version maintains the original structure, making it easier to read and maintain while still incorporating the necessary changes.

@@ -18,16 +20,24 @@ export const getClickHouseHost = () => getClickHouseHosts()[0]

export const getClient = <B extends boolean>({
web,
clickhouse_settings,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider simplifying the getClient function and handling of clickhouse_settings.

The new code introduces additional complexity in several ways:

  1. Increased Parameter Complexity: The getClient function now takes an additional clickhouse_settings parameter, complicating the function signature and requiring the caller to provide this parameter.

  2. Nested Object Spread: The nested object spread within the clickhouse_settings object can be error-prone and harder to read, especially for those unfamiliar with the spread operator or the specific structure of clickhouse_settings.

  3. Default Value Handling: Introducing a default value for max_execution_time using a constant DEFAULT_CLICKHOUSE_MAX_EXECUTION_TIME adds another layer of complexity, as the default value is now managed in two places: the environment variable and the constant.

  4. Return Type Change: Changing the return type of the fetchData function from Promise<T> to Promise<{ data: T }> affects all places where fetchData is called, requiring updates to handle the new structure of the returned data.

To address these issues while maintaining readability, consider the following simplified approach:

import type { ClickHouseClient, DataFormat } from '@clickhouse/client';
import { createClient } from '@clickhouse/client';
import type { ClickHouseSettings, QueryParams } from '@clickhouse/client-common';
import { createClient as createClientWeb } from '@clickhouse/client-web';
import type { WebClickHouseClient } from '@clickhouse/client-web/dist/client';
import { cache } from 'react';

const DEFAULT_CLICKHOUSE_MAX_EXECUTION_TIME = 60;

export const getClickHouseHosts = () => {
  const hosts = (process.env.CLICKHOUSE_HOST || '')
    .split(',')
    .map((host) => host.trim())
    .filter(Boolean);
  return hosts;
};

export const getClickHouseHost = () => getClickHouseHosts()[0];

export const getClient = <B extends boolean>({
  web,
  clickhouse_settings = {},
}: {
  web?: B;
  clickhouse_settings?: ClickHouseSettings;
}): B extends true ? WebClickHouseClient : ClickHouseClient => {
  const client = web === true ? createClientWeb : createClient;
  return client({
    host: getClickHouseHost(),
    username: process.env.CLICKHOUSE_USER ?? 'default',
    password: process.env.CLICKHOUSE_PASSWORD ?? '',
    clickhouse_settings: {
      max_execution_time: parseInt(
        process.env.CLICKHOUSE_MAX_EXECUTION_TIME ?? DEFAULT_CLICKHOUSE_MAX_EXECUTION_TIME.toString()
      ),
      ...clickhouse_settings,
    },
  }) as B extends true ? WebClickHouseClient : ClickHouseClient;
};

export const QUERY_COMMENT = '/* { "client": "clickhouse-monitoring" } */ ';

export const fetchData = async <T>({
  query,
  query_params,
  format = 'JSONEachRow',
  clickhouse_settings,
}: QueryParams): Promise<T> => {
  const start = new Date();
  const client = getClient({ web: false, clickhouse_settings });
  const resultSet = await client.query({
    query: QUERY_COMMENT + query,
    format,
    query_params,
    clickhouse_settings,
  });
  const data = await resultSet.json<T>();
  const query_id = resultSet.query_id;

  if (data && 'statistics' in data) {
    console.debug(
      `<-- Response (${query_id}):`,
      'rows',
      JSON.stringify(data.statistics),
      '\n'
    );
  } else if (typeof data === 'object' && data.hasOwnProperty('rows')) {
    console.debug(`<-- Response (${query_id}): ${data.rows} rows\n`);
  } else {
    console.debug(`<-- Response (${query_id}):`, data, '\n`);
  }

  return data;
};

export const fetchDataWithCache = () => cache(fetchData);

export const query = async (
  query: string,
  params: Record<string, unknown> = {},
  format: DataFormat = 'JSON'
) => {
  const resultSet = await getClient({ web: false }).query({
    query,
    format,
    query_params: params,
  });
  return resultSet.json();
};

This approach maintains the new functionality while keeping the code more readable and easier to maintain.

const first = rows?.[0]
if (!rows || !first) return null
const first = data?.[0]
if (!data || !first) return null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!data || !first) return null
if (!data || !first) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

query: sql,
format: 'JSONEachRow',
clickhouse_settings: { use_query_cache: 1, query_cache_ttl: 120 },
})

if (!data || !data.length || !data?.[0]?.['count()']) return null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (!data || !data.length || !data?.[0]?.['count()']) return null
if (!data || !data.length || !data?.[0]?.['count()']) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

if (!data || !data.length || !data?.[0]?.['count()']) return null

const count = data[0]['count()'] || 0
if (count == 0) return null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use block braces for ifs, whiles, etc. (use-braces)

Suggested change
if (count == 0) return null
if (count == 0) {


ExplanationIt is recommended to always use braces and create explicit statement blocks.

Using the allowed syntax to just write a single statement can lead to very confusing
situations, especially where subsequently a developer might add another statement
while forgetting to add the braces (meaning that this wouldn't be included in the condition).

@duyet duyet merged commit 741f41c into main Jun 3, 2024
21 checks passed
@duyet duyet deleted the chore/ui branch June 3, 2024 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant