mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
chore: Add @eslint-react/eslint-plugin recommended rules (#2069)
This commit is contained in:
parent
24767c5886
commit
74536b3af5
12 changed files with 157 additions and 152 deletions
|
|
@ -122,6 +122,10 @@ export default [
|
|||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs['core-web-vitals'].rules,
|
||||
...reactHooksPlugin.configs.recommended.rules,
|
||||
...eslintReactPlugin.configs.recommended.rules,
|
||||
// Disable rules from eslint-plugin-react-hooks that have equivalent rules in @eslint-react
|
||||
...eslintReactPlugin.configs['disable-conflict-eslint-plugin-react-hooks'].rules,
|
||||
...eslintReactPlugin.configs['recommended-type-checked'].rules,
|
||||
'react-hooks/set-state-in-effect': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
'react-hook-form/no-use-watch': 'error',
|
||||
|
|
@ -206,6 +210,7 @@ export default [
|
|||
rules: {
|
||||
// Drop date rules — new Date() / Date.now() are fine in tests
|
||||
'no-restricted-syntax': ['error', ...UI_SYNTAX_RESTRICTIONS],
|
||||
'@eslint-react/component-hook-factories': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -368,6 +368,7 @@ function InsertsTab({
|
|||
}
|
||||
toolbarPrefix={[
|
||||
<SegmentedControl
|
||||
key="inserts-by-toolbar"
|
||||
size="xs"
|
||||
value={insertsBy ?? 'queries'}
|
||||
onChange={value => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
FormEvent,
|
||||
FormEventHandler,
|
||||
Fragment,
|
||||
memo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
|
|
@ -2005,7 +2006,7 @@ function DBSearchPage() {
|
|||
</Text>
|
||||
<Grid>
|
||||
{whereSuggestions!.map(s => (
|
||||
<>
|
||||
<Fragment key={s.corrected()}>
|
||||
<Grid.Col span={10}>
|
||||
<Text>{s.userMessage('where')}</Text>
|
||||
</Grid.Col>
|
||||
|
|
@ -2018,7 +2019,7 @@ function DBSearchPage() {
|
|||
Accept
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
</>
|
||||
</Fragment>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -309,6 +309,20 @@ const LegendRenderer = memo<{
|
|||
|
||||
export const HARD_LINES_LIMIT = 60;
|
||||
|
||||
const StackedBarWithOverlap = (props: BarProps) => {
|
||||
const { x, y, width, height, fill } = props;
|
||||
// Add a tiny bit to the height to create overlap. Otherwise there's a gap
|
||||
return (
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height && height > 0 ? height + 0.5 : 0}
|
||||
fill={fill}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const MemoChart = memo(function MemoChart({
|
||||
graphResults,
|
||||
setIsClickActive,
|
||||
|
|
@ -375,20 +389,6 @@ export const MemoChart = memo(function MemoChart({
|
|||
const strokeDasharray = lineData[lineDataIndex]?.isDashed ? '4 3' : '0';
|
||||
const seriesName = lineData[lineDataIndex]?.displayName ?? key;
|
||||
|
||||
const StackedBarWithOverlap = (props: BarProps) => {
|
||||
const { x, y, width, height, fill } = props;
|
||||
// Add a tiny bit to the height to create overlap. Otherwise there's a gap
|
||||
return (
|
||||
<rect
|
||||
x={x}
|
||||
y={y}
|
||||
width={width}
|
||||
height={height && height > 0 ? height + 0.5 : 0}
|
||||
fill={fill}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return displayType === 'stacked_bar' ? (
|
||||
<Bar
|
||||
key={key}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export const SectionWrapper: React.FC<
|
|||
React.PropsWithChildren<{ title?: React.ReactNode }>
|
||||
> = ({ children, title }) => (
|
||||
<div className={styles.panelSectionWrapper}>
|
||||
{title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
|
||||
{!!title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const SettingContainer = ({
|
|||
<Group align="center" justify="space-between">
|
||||
<div style={{ flex: 1 }}>
|
||||
{label}
|
||||
{description && (
|
||||
{!!description && (
|
||||
<Text size="xs" mt={2}>
|
||||
{description}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable @eslint-react/no-create-ref */
|
||||
import * as React from 'react';
|
||||
import { useImperativeHandle } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
|
|
|||
|
|
@ -478,7 +478,7 @@ export default function DBDeltaChart({
|
|||
{/* Legend */}
|
||||
<Flex gap="md" align="center" mt={2} mb="xs" wrap="wrap">
|
||||
{legendPrefix}
|
||||
{legendPrefix && (
|
||||
{!!legendPrefix && (
|
||||
<Box
|
||||
h={12}
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -1329,6 +1329,11 @@ const DBSearchPageFiltersComponent = ({
|
|||
);
|
||||
}, [filterState, source, parentSpanIdExpr]);
|
||||
|
||||
const { grouped, nonGrouped } = useMemo(
|
||||
() => groupFacetsByBaseName(shownFacets),
|
||||
[shownFacets],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box className={classes.filtersPanel} style={{ width: `${size}%` }}>
|
||||
<div className={resizeStyles.resizeHandle} onMouseDown={startResize} />
|
||||
|
|
@ -1480,135 +1485,127 @@ const DBSearchPageFiltersComponent = ({
|
|||
)
|
||||
)}
|
||||
{/* Show facets even when loading to ensure pinned filters are visible while loading */}
|
||||
{(() => {
|
||||
const { grouped, nonGrouped } = groupFacetsByBaseName(shownFacets);
|
||||
<>
|
||||
{/* Render grouped facets as nested filter groups */}
|
||||
{grouped.map(group => (
|
||||
<NestedFilterGroup
|
||||
key={group.key}
|
||||
data-testid={`nested-filter-group-${group.key}`}
|
||||
name={group.key}
|
||||
childFilters={group.children}
|
||||
selectedValues={group.children.reduce(
|
||||
(acc, child) => {
|
||||
acc[child.key] = filterState[child.key]
|
||||
? filterState[child.key]
|
||||
: { included: new Set(), excluded: new Set() };
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
{
|
||||
included: Set<string | boolean>;
|
||||
excluded: Set<string | boolean>;
|
||||
}
|
||||
>,
|
||||
)}
|
||||
onChange={(key, value) => {
|
||||
setFilterValue(key, value);
|
||||
}}
|
||||
onClearClick={key => clearFilter(key)}
|
||||
onOnlyClick={(key, value) => {
|
||||
setFilterValue(key, value, 'only');
|
||||
}}
|
||||
onExcludeClick={(key, value) => {
|
||||
setFilterValue(key, value, 'exclude');
|
||||
}}
|
||||
onPinClick={(key, value) => toggleFilterPin(key, value)}
|
||||
isPinned={(key, value) => isFilterPinned(key, value)}
|
||||
onFieldPinClick={key => toggleFieldPin(key)}
|
||||
isFieldPinned={key => isFieldPinned(key)}
|
||||
onColumnToggle={onColumnToggle}
|
||||
displayedColumns={displayedColumns}
|
||||
onLoadMore={loadMoreFilterValuesForKey}
|
||||
loadMoreLoading={group.children.reduce(
|
||||
(acc, child) => {
|
||||
acc[child.key] = loadMoreLoadingKeys.has(child.key);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
)}
|
||||
hasLoadedMore={group.children.reduce(
|
||||
(acc, child) => {
|
||||
acc[child.key] = Boolean(extraFacets[child.key]);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
)}
|
||||
isDefaultExpanded={
|
||||
// open by default if has selected values or pinned children
|
||||
group.children.some(
|
||||
child =>
|
||||
(filterState[child.key] &&
|
||||
(filterState[child.key].included.size > 0 ||
|
||||
filterState[child.key].excluded.size > 0)) ||
|
||||
isFieldPinned(child.key),
|
||||
)
|
||||
}
|
||||
chartConfig={chartConfig}
|
||||
isLive={isLive}
|
||||
/>
|
||||
))}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Render grouped facets as nested filter groups */}
|
||||
{grouped.map(group => (
|
||||
<NestedFilterGroup
|
||||
key={group.key}
|
||||
data-testid={`nested-filter-group-${group.key}`}
|
||||
name={group.key}
|
||||
childFilters={group.children}
|
||||
selectedValues={group.children.reduce(
|
||||
(acc, child) => {
|
||||
acc[child.key] = filterState[child.key]
|
||||
? filterState[child.key]
|
||||
: { included: new Set(), excluded: new Set() };
|
||||
return acc;
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
{
|
||||
included: Set<string | boolean>;
|
||||
excluded: Set<string | boolean>;
|
||||
}
|
||||
>,
|
||||
)}
|
||||
onChange={(key, value) => {
|
||||
setFilterValue(key, value);
|
||||
}}
|
||||
onClearClick={key => clearFilter(key)}
|
||||
onOnlyClick={(key, value) => {
|
||||
setFilterValue(key, value, 'only');
|
||||
}}
|
||||
onExcludeClick={(key, value) => {
|
||||
setFilterValue(key, value, 'exclude');
|
||||
}}
|
||||
onPinClick={(key, value) => toggleFilterPin(key, value)}
|
||||
isPinned={(key, value) => isFilterPinned(key, value)}
|
||||
onFieldPinClick={key => toggleFieldPin(key)}
|
||||
isFieldPinned={key => isFieldPinned(key)}
|
||||
onColumnToggle={onColumnToggle}
|
||||
displayedColumns={displayedColumns}
|
||||
onLoadMore={loadMoreFilterValuesForKey}
|
||||
loadMoreLoading={group.children.reduce(
|
||||
(acc, child) => {
|
||||
acc[child.key] = loadMoreLoadingKeys.has(child.key);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
)}
|
||||
hasLoadedMore={group.children.reduce(
|
||||
(acc, child) => {
|
||||
acc[child.key] = Boolean(extraFacets[child.key]);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, boolean>,
|
||||
)}
|
||||
isDefaultExpanded={
|
||||
// open by default if has selected values or pinned children
|
||||
group.children.some(
|
||||
child =>
|
||||
(filterState[child.key] &&
|
||||
(filterState[child.key].included.size > 0 ||
|
||||
filterState[child.key].excluded.size > 0)) ||
|
||||
isFieldPinned(child.key),
|
||||
)
|
||||
}
|
||||
chartConfig={chartConfig}
|
||||
isLive={isLive}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Render non-grouped facets as regular filter groups */}
|
||||
{nonGrouped.map(facet => (
|
||||
<FilterGroup
|
||||
key={facet.key}
|
||||
data-testid={`filter-group-${facet.key}`}
|
||||
name={cleanedFacetName(facet.key)}
|
||||
options={facet.value.map(value => ({
|
||||
value,
|
||||
label: value.toString(),
|
||||
}))}
|
||||
optionsLoading={isFacetsLoading}
|
||||
selectedValues={
|
||||
filterState[facet.key]
|
||||
? filterState[facet.key]
|
||||
: { included: new Set(), excluded: new Set() }
|
||||
}
|
||||
onChange={value => {
|
||||
setFilterValue(facet.key, value);
|
||||
}}
|
||||
onClearClick={() => clearFilter(facet.key)}
|
||||
onOnlyClick={value => {
|
||||
setFilterValue(facet.key, value, 'only');
|
||||
}}
|
||||
onExcludeClick={value => {
|
||||
setFilterValue(facet.key, value, 'exclude');
|
||||
}}
|
||||
onPinClick={value => toggleFilterPin(facet.key, value)}
|
||||
isPinned={value => isFilterPinned(facet.key, value)}
|
||||
onFieldPinClick={() => toggleFieldPin(facet.key)}
|
||||
isFieldPinned={isFieldPinned(facet.key)}
|
||||
onColumnToggle={
|
||||
onColumnToggle
|
||||
? () => onColumnToggle(facet.key)
|
||||
: undefined
|
||||
}
|
||||
isColumnDisplayed={displayedColumns?.includes(facet.key)}
|
||||
onLoadMore={loadMoreFilterValuesForKey}
|
||||
loadMoreLoading={loadMoreLoadingKeys.has(facet.key)}
|
||||
hasLoadedMore={Boolean(extraFacets[facet.key])}
|
||||
isDefaultExpanded={
|
||||
// open by default if PK, or has selected values
|
||||
isFieldPrimary(tableMetadata, facet.key) ||
|
||||
isFieldPinned(facet.key) ||
|
||||
(filterState[facet.key] &&
|
||||
(filterState[facet.key].included.size > 0 ||
|
||||
filterState[facet.key].excluded.size > 0 ||
|
||||
filterState[facet.key].range != null))
|
||||
}
|
||||
chartConfig={chartConfig}
|
||||
isLive={isLive}
|
||||
onRangeChange={range => setFilterRange(facet.key, range)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
{/* Render non-grouped facets as regular filter groups */}
|
||||
{nonGrouped.map(facet => (
|
||||
<FilterGroup
|
||||
key={facet.key}
|
||||
data-testid={`filter-group-${facet.key}`}
|
||||
name={cleanedFacetName(facet.key)}
|
||||
options={facet.value.map(value => ({
|
||||
value,
|
||||
label: value.toString(),
|
||||
}))}
|
||||
optionsLoading={isFacetsLoading}
|
||||
selectedValues={
|
||||
filterState[facet.key]
|
||||
? filterState[facet.key]
|
||||
: { included: new Set(), excluded: new Set() }
|
||||
}
|
||||
onChange={value => {
|
||||
setFilterValue(facet.key, value);
|
||||
}}
|
||||
onClearClick={() => clearFilter(facet.key)}
|
||||
onOnlyClick={value => {
|
||||
setFilterValue(facet.key, value, 'only');
|
||||
}}
|
||||
onExcludeClick={value => {
|
||||
setFilterValue(facet.key, value, 'exclude');
|
||||
}}
|
||||
onPinClick={value => toggleFilterPin(facet.key, value)}
|
||||
isPinned={value => isFilterPinned(facet.key, value)}
|
||||
onFieldPinClick={() => toggleFieldPin(facet.key)}
|
||||
isFieldPinned={isFieldPinned(facet.key)}
|
||||
onColumnToggle={
|
||||
onColumnToggle ? () => onColumnToggle(facet.key) : undefined
|
||||
}
|
||||
isColumnDisplayed={displayedColumns?.includes(facet.key)}
|
||||
onLoadMore={loadMoreFilterValuesForKey}
|
||||
loadMoreLoading={loadMoreLoadingKeys.has(facet.key)}
|
||||
hasLoadedMore={Boolean(extraFacets[facet.key])}
|
||||
isDefaultExpanded={
|
||||
// open by default if PK, or has selected values
|
||||
isFieldPrimary(tableMetadata, facet.key) ||
|
||||
isFieldPinned(facet.key) ||
|
||||
(filterState[facet.key] &&
|
||||
(filterState[facet.key].included.size > 0 ||
|
||||
filterState[facet.key].excluded.size > 0 ||
|
||||
filterState[facet.key].range != null))
|
||||
}
|
||||
chartConfig={chartConfig}
|
||||
isLive={isLive}
|
||||
onRangeChange={range => setFilterRange(facet.key, range)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
|
|
|||
|
|
@ -40,17 +40,17 @@ export default function EmptyState({
|
|||
}: EmptyStateProps) {
|
||||
const inner = (
|
||||
<Stack align="center" gap="xs">
|
||||
{icon && (
|
||||
{!!icon && (
|
||||
<ThemeIcon size={56} radius="xl" variant="light" color="gray">
|
||||
{icon}
|
||||
</ThemeIcon>
|
||||
)}
|
||||
{title && (
|
||||
{!!title && (
|
||||
<Title order={3} fw={600} size="xl" maw={600}>
|
||||
{title}
|
||||
</Title>
|
||||
)}
|
||||
{description && (
|
||||
{!!description && (
|
||||
<Text size="sm" c="dimmed" ta="center" maw={600}>
|
||||
{description}
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export const SectionWrapper: React.FC<
|
|||
React.PropsWithChildren<{ title?: React.ReactNode }>
|
||||
> = ({ children, title }) => (
|
||||
<div className={styles.panelSectionWrapper}>
|
||||
{title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
|
||||
{!!title && <div className={styles.panelSectionWrapperTitle}>{title}</div>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export const TableCellButton: React.FC<{
|
|||
}> = ({ onClick, title, label, biIcon }) => {
|
||||
return (
|
||||
<button className={styles.tableCellButton} title={title} onClick={onClick}>
|
||||
{label && <span>{label}</span>}
|
||||
{!!label && <span>{label}</span>}
|
||||
{biIcon === 'chevron-up' && <IconChevronUp size={14} />}
|
||||
{biIcon === 'chevron-down' && <IconChevronDown size={14} />}
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Reference in a new issue