This is an automated email from the ASF dual-hosted git repository.

vogievetsky pushed a commit to branch segment_timeline2
in repository https://gitbox.apache.org/repos/asf/druid.git

commit 5855a5bb7ff47858e97483fa054c834dae539482
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Mon Nov 18 15:28:05 2024 -0800

    fix render spamming
---
 .../segment-timeline/segment-bar-chart-render.tsx  | 58 +++++++++++++++++-----
 .../segment-timeline/segment-bar-chart.tsx         |  2 +-
 .../segment-timeline/segment-timeline.tsx          |  6 ++-
 .../table-filterable-cell.tsx                      |  2 +-
 web-console/src/console-application.tsx            | 15 ++++--
 .../src/react-table/react-table-utils.spec.ts      |  4 ++
 web-console/src/react-table/react-table-utils.ts   | 43 ++++++++++++----
 web-console/src/utils/general.tsx                  |  4 ++
 .../views/datasources-view/datasources-view.tsx    | 16 ++++--
 .../src/views/segments-view/segments-view.tsx      | 27 ++++++++--
 10 files changed, 142 insertions(+), 35 deletions(-)

diff --git 
a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx 
b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
index df4842988c3..ea6310cc843 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart-render.tsx
@@ -31,6 +31,7 @@ import type { Rule } from '../../druid-models';
 import { getDatasourceColor, RuleUtil } from '../../druid-models';
 import { useClock, useGlobalEventListener } from '../../hooks';
 import {
+  arraysEqualByElement,
   clamp,
   day,
   Duration,
@@ -215,6 +216,7 @@ export interface SegmentBarChartRenderProps {
   shownIntervalStat: IntervalStat;
   shownDatasource: string | undefined;
   changeShownDatasource(datasource: string | undefined): void;
+  getActionButton?(start: Date, end: Date, datasource?: string): ReactNode;
 }
 
 export const SegmentBarChartRender = function SegmentBarChartRender(
@@ -229,13 +231,42 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     datasourceRules,
     shownDatasource,
     changeShownDatasource,
+    getActionButton,
   } = props;
   const [mouseDownAt, setMouseDownAt] = useState<
     { time: Date; action: 'select' | 'shift' } | undefined
   >();
   const [selection, setSelection] = useState<SelectionRange | undefined>();
-  const [shiftOffset, setShiftOffset] = useState<number | undefined>();
+
+  function setSelectionIfNeeded(newSelection: SelectionRange) {
+    if (
+      selection &&
+      selection.start.valueOf() === newSelection.start.valueOf() &&
+      selection.end.valueOf() === newSelection.end.valueOf() &&
+      selection.done === newSelection.done
+    ) {
+      return;
+    }
+    setSelection(newSelection);
+  }
+
   const [bubbleInfo, setBubbleInfo] = useState<BubbleInfo | undefined>();
+
+  function setBubbleInfoIfNeeded(newBubbleInfo: BubbleInfo) {
+    if (
+      bubbleInfo &&
+      bubbleInfo.start.valueOf() === newBubbleInfo.start.valueOf() &&
+      bubbleInfo.end.valueOf() === newBubbleInfo.end.valueOf() &&
+      bubbleInfo.timeLabel === newBubbleInfo.timeLabel &&
+      arraysEqualByElement(bubbleInfo.intervalBars, newBubbleInfo.intervalBars)
+    ) {
+      return;
+    }
+    setBubbleInfo(newBubbleInfo);
+  }
+
+  const [shiftOffset, setShiftOffset] = useState<number | undefined>();
+
   const now = useClock(minute.canonicalLength);
   const svgRef = useRef<SVGSVGElement | null>(null);
 
@@ -377,9 +408,15 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
         setShiftOffset(mouseDownAt.time.valueOf() - b.valueOf());
       } else {
         if (mouseDownAt.time < b) {
-          setSelection({ start: day.floor(mouseDownAt.time, TZ_UTC), end: 
day.ceil(b, TZ_UTC) });
+          setSelectionIfNeeded({
+            start: day.floor(mouseDownAt.time, TZ_UTC),
+            end: day.ceil(b, TZ_UTC),
+          });
         } else {
-          setSelection({ start: day.floor(b, TZ_UTC), end: 
day.ceil(mouseDownAt.time, TZ_UTC) });
+          setSelectionIfNeeded({
+            start: day.floor(b, TZ_UTC),
+            end: day.ceil(mouseDownAt.time, TZ_UTC),
+          });
         }
       }
     } else if (!selection) {
@@ -414,7 +451,7 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
             intervalBars = hoverBar ? [hoverBar] : bars;
           }
         }
-        setBubbleInfo({
+        setBubbleInfoIfNeeded({
           start,
           end,
           timeLabel: start.toISOString().slice(0, shifter === day ? 10 : 7),
@@ -497,8 +534,6 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
     };
   }
 
-  console.log('Bar chart render');
-
   let hoveredOpenOn: { x: number; y: number; title?: string; text: ReactNode } 
| undefined;
   if (svgRef.current) {
     const rect = svgRef.current.getBoundingClientRect();
@@ -589,12 +624,11 @@ export const SegmentBarChartRender = function 
SegmentBarChartRender(
                     changeDateRange([selection.start, selection.end]);
                   }}
                 />
-                <Button
-                  text="Open in segments view"
-                  small
-                  rightIcon={IconNames.ARROW_TOP_RIGHT}
-                  onClick={() => null}
-                />
+                {getActionButton?.(
+                  selection.start,
+                  selection.end,
+                  datasources.length === 1 ? datasources[0] : undefined,
+                )}
               </div>
             )}
           </>
diff --git a/web-console/src/components/segment-timeline/segment-bar-chart.tsx 
b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
index 20b1dec7045..9be6aab54fb 100644
--- a/web-console/src/components/segment-timeline/segment-bar-chart.tsx
+++ b/web-console/src/components/segment-timeline/segment-bar-chart.tsx
@@ -32,7 +32,7 @@ import { SegmentBarChartRender } from 
'./segment-bar-chart-render';
 
 import './segment-bar-chart.scss';
 
-interface SegmentBarChartProps
+export interface SegmentBarChartProps
   extends Omit<SegmentBarChartRenderProps, 'intervalRows' | 'datasourceRules'> 
{
   capabilities: Capabilities;
 }
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx 
b/web-console/src/components/segment-timeline/segment-timeline.tsx
index 72be20a3d83..18bda4f838b 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -52,6 +52,7 @@ import { Loader } from '../loader/loader';
 
 import type { IntervalStat } from './common';
 import { formatIsoDateOnly, getIntervalStatTitle, INTERVAL_STATS } from 
'./common';
+import type { SegmentBarChartProps } from './segment-bar-chart';
 import { SegmentBarChart } from './segment-bar-chart';
 
 import './segment-timeline.scss';
@@ -80,13 +81,13 @@ function dateRangesEqual(dr1: NonNullDateRange, dr2: 
NonNullDateRange): boolean
   return dr1[0].valueOf() === dr2[0].valueOf() && dr2[1].valueOf() === 
dr2[1].valueOf();
 }
 
-interface SegmentTimelineProps {
+interface SegmentTimelineProps extends Pick<SegmentBarChartProps, 
'getActionButton'> {
   capabilities: Capabilities;
   datasource: string | undefined;
 }
 
 export const SegmentTimeline = function SegmentTimeline(props: 
SegmentTimelineProps) {
-  const { capabilities, datasource } = props;
+  const { capabilities, datasource, ...otherProps } = props;
   const [stage, setStage] = useState<Stage | undefined>();
   const [activeSegmentStat, setActiveSegmentStat] = 
useState<IntervalStat>('size');
   const [shownDatasource, setShownDatasource] = useState<string | 
undefined>(datasource);
@@ -351,6 +352,7 @@ export const SegmentTimeline = function 
SegmentTimeline(props: SegmentTimelinePr
               shownIntervalStat={activeSegmentStat}
               shownDatasource={shownDatasource}
               changeShownDatasource={setShownDatasource}
+              {...otherProps}
             />
           )}
           {initDatasourceDateRangeState.isLoading() && <Loader />}
diff --git 
a/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx 
b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
index 631fa224aaf..c03f0038cc7 100644
--- a/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
+++ b/web-console/src/components/table-filterable-cell/table-filterable-cell.tsx
@@ -27,7 +27,7 @@ import { Deferred } from '../deferred/deferred';
 
 import './table-filterable-cell.scss';
 
-const FILTER_MODES: FilterMode[] = ['=', '!=', '<=', '>='];
+const FILTER_MODES: FilterMode[] = ['=', '!=', '<', '>='];
 const FILTER_MODES_NO_COMPARISONS: FilterMode[] = ['=', '!='];
 
 export interface TableFilterableCellProps {
diff --git a/web-console/src/console-application.tsx 
b/web-console/src/console-application.tsx
index 95336713dbb..acb7d9c81a3 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -182,12 +182,21 @@ export class ConsoleApplication extends 
React.PureComponent<
     changeTabWithFilter('datasources', [{ id: 'datasource', value: 
`=${datasource}` }]);
   };
 
-  private readonly goToSegments = (datasource: string, onlyUnavailable = 
false) => {
+  private readonly goToSegments = ({
+    start,
+    end,
+    datasource,
+  }: {
+    start?: Date;
+    end?: Date;
+    datasource?: string;
+  }) => {
     changeTabWithFilter(
       'segments',
       compact([
-        { id: 'datasource', value: `=${datasource}` },
-        onlyUnavailable ? { id: 'is_available', value: '=false' } : undefined,
+        start && { id: 'start', value: `>=${start.toISOString()}` },
+        end && { id: 'end', value: `<${end.toISOString()}` },
+        datasource && { id: 'datasource', value: `=${datasource}` },
       ]),
     );
   };
diff --git a/web-console/src/react-table/react-table-utils.spec.ts 
b/web-console/src/react-table/react-table-utils.spec.ts
index 0a1bbf3f9ad..1dd245b49f7 100644
--- a/web-console/src/react-table/react-table-utils.spec.ts
+++ b/web-console/src/react-table/react-table-utils.spec.ts
@@ -75,5 +75,9 @@ describe('react-table-utils', () => {
       { id: 'x', value: '~y' },
       { id: 'z', value: '=w&' },
     ]);
+    expect(stringToTableFilters('x<3&y<=3')).toEqual([
+      { id: 'x', value: '<3' },
+      { id: 'y', value: '<=3' },
+    ]);
   });
 });
diff --git a/web-console/src/react-table/react-table-utils.ts 
b/web-console/src/react-table/react-table-utils.ts
index dc3debdf32c..ec1272b2282 100644
--- a/web-console/src/react-table/react-table-utils.ts
+++ b/web-console/src/react-table/react-table-utils.ts
@@ -31,9 +31,9 @@ export const STANDARD_TABLE_PAGE_SIZE_OPTIONS = [50, 100, 
200];
 export const SMALL_TABLE_PAGE_SIZE = 25;
 export const SMALL_TABLE_PAGE_SIZE_OPTIONS = [25, 50, 100];
 
-export type FilterMode = '~' | '=' | '!=' | '<=' | '>=';
+export type FilterMode = '~' | '=' | '!=' | '<' | '<=' | '>' | '>=';
 
-export const FILTER_MODES: FilterMode[] = ['~', '=', '!=', '<=', '>='];
+export const FILTER_MODES: FilterMode[] = ['~', '=', '!=', '<', '<=', '>', 
'>='];
 export const FILTER_MODES_NO_COMPARISON: FilterMode[] = ['~', '=', '!='];
 
 export function filterModeToIcon(mode: FilterMode): IconName {
@@ -44,8 +44,12 @@ export function filterModeToIcon(mode: FilterMode): IconName 
{
       return IconNames.EQUALS;
     case '!=':
       return IconNames.NOT_EQUAL_TO;
+    case '<':
+      return IconNames.LESS_THAN;
     case '<=':
       return IconNames.LESS_THAN_OR_EQUAL_TO;
+    case '>':
+      return IconNames.GREATER_THAN;
     case '>=':
       return IconNames.GREATER_THAN_OR_EQUAL_TO;
     default:
@@ -61,8 +65,12 @@ export function filterModeToTitle(mode: FilterMode): string {
       return 'Equals';
     case '!=':
       return 'Not equals';
+    case '<':
+      return 'Less than';
     case '<=':
       return 'Less than or equal';
+    case '>':
+      return 'Greater than';
     case '>=':
       return 'Greater than or equal';
     default:
@@ -88,7 +96,7 @@ export function parseFilterModeAndNeedle(
   filter: Filter,
   loose = false,
 ): FilterModeAndNeedle | undefined {
-  const m = /^(~|=|!=|<=|>=)?(.*)$/.exec(String(filter.value));
+  const m = /^(~|=|!=|<(?!=)|<=|>(?!=)|>=)?(.*)$/.exec(String(filter.value));
   if (!m) return;
   if (!loose && !m[2]) return;
   const mode = (m[1] as FilterMode) || '~';
@@ -111,21 +119,28 @@ export function booleanCustomTableFilter(filter: Filter, 
value: unknown): boolea
   const modeAndNeedle = parseFilterModeAndNeedle(filter);
   if (!modeAndNeedle) return true;
   const { mode, needle } = modeAndNeedle;
+  const strValue = String(value);
   switch (mode) {
     case '=':
-      return String(value) === needle;
+      return strValue === needle;
 
     case '!=':
-      return String(value) !== needle;
+      return strValue !== needle;
+
+    case '<':
+      return strValue < needle;
 
     case '<=':
-      return String(value) <= needle;
+      return strValue <= needle;
+
+    case '>':
+      return strValue > needle;
 
     case '>=':
-      return String(value) >= needle;
+      return strValue >= needle;
 
     default:
-      return caseInsensitiveContains(String(value), needle);
+      return caseInsensitiveContains(strValue, needle);
   }
 }
 
@@ -141,9 +156,15 @@ export function sqlQueryCustomTableFilter(filter: Filter): 
SqlExpression | undef
     case '!=':
       return column.unequal(needle);
 
+    case '<':
+      return column.lessThan(needle);
+
     case '<=':
       return column.lessThanOrEqual(needle);
 
+    case '>':
+      return column.greaterThan(needle);
+
     case '>=':
       return column.greaterThanOrEqual(needle);
 
@@ -164,9 +185,11 @@ export function tableFiltersToString(tableFilters: 
Filter[]): string {
 
 export function stringToTableFilters(str: string | undefined): Filter[] {
   if (!str) return [];
-  // '~' | '=' | '!=' | '<=' | '>=';
+  // '~' | '=' | '!=' | '<' | '<=' | '>' | '>=';
   return filterMap(str.split('&'), clause => {
-    const m = /^(\w+)((?:~|=|!=|<=|>=).*)$/.exec(clause.replace(/%2[56]/g, 
decodeURIComponent));
+    const m = /^(\w+)((?:~|=|!=|<(?!=)|<=|>(?!=)|>=).*)$/.exec(
+      clause.replace(/%2[56]/g, decodeURIComponent),
+    );
     if (!m) return;
     return { id: m[1], value: m[2] };
   });
diff --git a/web-console/src/utils/general.tsx 
b/web-console/src/utils/general.tsx
index 9e2e5e7f525..89a05467649 100644
--- a/web-console/src/utils/general.tsx
+++ b/web-console/src/utils/general.tsx
@@ -59,6 +59,10 @@ export function isSimpleArray(a: any): a is (string | number 
| boolean)[] {
   );
 }
 
+export function arraysEqualByElement<T>(xs: T[], ys: T[]): boolean {
+  return xs.length === ys.length && xs.every((x, i) => x === ys[i]);
+}
+
 export function wait(ms: number): Promise<void> {
   return new Promise(resolve => {
     setTimeout(resolve, ms);
diff --git a/web-console/src/views/datasources-view/datasources-view.tsx 
b/web-console/src/views/datasources-view/datasources-view.tsx
index 9891368e585..064f95e673e 100644
--- a/web-console/src/views/datasources-view/datasources-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { FormGroup, InputGroup, Intent, MenuItem, Switch, Tag } from 
'@blueprintjs/core';
+import { Button, FormGroup, InputGroup, Intent, MenuItem, Switch, Tag } from 
'@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
 import { SqlQuery, T } from '@druid-toolkit/query';
 import { sum } from 'd3-array';
@@ -305,7 +305,7 @@ export interface DatasourcesViewProps {
   onFiltersChange(filters: Filter[]): void;
   goToQuery(queryWithContext: QueryWithContext): void;
   goToTasks(datasource?: string): void;
-  goToSegments(datasource: string, onlyUnavailable?: boolean): void;
+  goToSegments(options: { start?: Date; end?: Date; datasource?: string }): 
void;
   capabilities: Capabilities;
 }
 
@@ -1692,7 +1692,7 @@ GROUP BY 1, 2`;
   }
 
   render() {
-    const { capabilities } = this.props;
+    const { capabilities, goToSegments } = this.props;
     const {
       showUnused,
       visibleColumns,
@@ -1752,6 +1752,16 @@ GROUP BY 1, 2`;
             <SegmentTimeline
               capabilities={capabilities}
               datasource={showSegmentTimeline.datasource}
+              getActionButton={(start, end, datasource) => {
+                return (
+                  <Button
+                    text="Open in segments view"
+                    small
+                    rightIcon={IconNames.ARROW_TOP_RIGHT}
+                    onClick={() => goToSegments({ start, end, datasource })}
+                  />
+                );
+              }}
             />
           )}
           {this.renderDatasourcesTable()}
diff --git a/web-console/src/views/segments-view/segments-view.tsx 
b/web-console/src/views/segments-view/segments-view.tsx
index 3233c587a4e..56659415a25 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -989,6 +989,7 @@ export class SegmentsView extends 
React.PureComponent<SegmentsViewProps, Segment
   }
 
   render() {
+    const { capabilities, onFiltersChange } = this.props;
     const {
       segmentTableActionDialogId,
       datasourceTableActionDialogId,
@@ -996,9 +997,8 @@ export class SegmentsView extends 
React.PureComponent<SegmentsViewProps, Segment
       visibleColumns,
       showSegmentTimeline,
       showFullShardSpec,
+      groupByInterval,
     } = this.state;
-    const { capabilities } = this.props;
-    const { groupByInterval } = this.state;
 
     return (
       <div className="segments-view app-view">
@@ -1060,7 +1060,28 @@ export class SegmentsView extends 
React.PureComponent<SegmentsViewProps, Segment
           secondaryMinSize={10}
         >
           {showSegmentTimeline && (
-            <SegmentTimeline capabilities={capabilities} 
datasource={undefined} />
+            <SegmentTimeline
+              capabilities={capabilities}
+              datasource={undefined}
+              getActionButton={(start, end, datasource) => {
+                return (
+                  <Button
+                    text="Apply fitler to table"
+                    small
+                    rightIcon={IconNames.ARROW_DOWN}
+                    onClick={() =>
+                      onFiltersChange(
+                        compact([
+                          start && { id: 'start', value: 
`>=${start.toISOString()}` },
+                          end && { id: 'end', value: `<${end.toISOString()}` },
+                          datasource && { id: 'datasource', value: 
`=${datasource}` },
+                        ]),
+                      )
+                    }
+                  />
+                );
+              }}
+            />
           )}
           {this.renderSegmentsTable()}
         </SplitterLayout>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to