From da96170a10cf5631040389879aa4741676a3bdf5 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Mon, 15 Jul 2024 20:16:36 -0700 Subject: [PATCH 1/5] remove __time from min max query shortcut --- .../src/views/workbench-view/column-tree/column-tree.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web-console/src/views/workbench-view/column-tree/column-tree.tsx b/web-console/src/views/workbench-view/column-tree/column-tree.tsx index 839bd2b5ca4c..6684dd356d31 100644 --- a/web-console/src/views/workbench-view/column-tree/column-tree.tsx +++ b/web-console/src/views/workbench-view/column-tree/column-tree.tsx @@ -339,7 +339,8 @@ export class ColumnTree extends React.PureComponent Date: Tue, 16 Jul 2024 10:33:46 -0700 Subject: [PATCH 2/5] fix scrolling in retention rules dialog --- .../retention-dialog.spec.tsx.snap | 1288 +++++++++-------- .../retention-dialog/retention-dialog.scss | 18 +- .../retention-dialog/retention-dialog.tsx | 112 +- 3 files changed, 725 insertions(+), 693 deletions(-) diff --git a/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap b/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap index c22b15631553..92f290c4a277 100644 --- a/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap +++ b/web-console/src/dialogs/retention-dialog/__snapshots__/retention-dialog.spec.tsx.snap @@ -105,367 +105,398 @@ exports[`RetentionDialog matches snapshot 1`] = `
-
- -
-
-
+ + loadByPeriod(P1000Y+future, 2x) + + + +
+ +
- - - - - Open dropdown - - - - -
-
- - + -
+
+
+ + - - - + +
+
+
-
-
-
-
-
- -
- - - - - Open dropdown - - - - -
-
- -
-
+
+ + + + Open dropdown + +
+ +
+
+ +
+
+ + +
+
- + Add historical tier replication + + +
-
+
+
+ -
-
-
+ + + + + New rule + +
-
- -
- - -
-
- -
-

- The cluster default rules are evaluated if none of the above rules match. -

-
- -
+ edit + + ) + + -
+
+

+ The cluster default rules are evaluated if none of the above rules match. +

-
-
-
+
-
-
+ + + +
+
+
+
- -
- - - - - Open dropdown - - - - -
-
- -
-
- +
+
+
+
+
+
+
+
+ + + + Open dropdown + +
+ +
+
+ +
+
+ + +
+
diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.scss b/web-console/src/dialogs/retention-dialog/retention-dialog.scss index fb55a8d44556..e2db98ee8a95 100644 --- a/web-console/src/dialogs/retention-dialog/retention-dialog.scss +++ b/web-console/src/dialogs/retention-dialog/retention-dialog.scss @@ -29,10 +29,26 @@ display: flex; flex-direction: column; - .rule-editor { + .rule-form { + position: relative; + flex: 1; + + .rule-form-content { + position: absolute; + width: 100%; + height: 100%; + overflow: auto; + } + } + + .rule-editor:not(:last-child) { margin-bottom: 15px; } + .json-input { + margin-bottom: 10px; + } + .no-rules-message { font-style: italic; } diff --git a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx index cc9645bd79f2..ac231e8e9912 100644 --- a/web-console/src/dialogs/retention-dialog/retention-dialog.tsx +++ b/web-console/src/dialogs/retention-dialog/retention-dialog.tsx @@ -114,6 +114,24 @@ ORDER BY 1`, setCurrentRules(swapElements(currentRules, index, index + direction)); } + const defaultRuleRender = + datasource !== CLUSTER_DEFAULT_FAKE_DATASOURCE ? ( + + Cluster defaults (edit) + + } + > +

The cluster default rules are evaluated if none of the above rules match.

+ {currentTab === 'form' ? ( + defaultRules.map((rule, index) => ) + ) : ( + + )} +
+ ) : undefined; + return ( {currentTab === 'form' ? ( - - {currentRules.length ? ( - currentRules.map((rule, index) => ( - changeRule(r, index)} - onDelete={() => deleteRule(index)} - moveUp={index > 0 ? () => moveRule(index, -1) : undefined} - moveDown={index < currentRules.length - 1 ? () => moveRule(index, 1) : undefined} - /> - )) - ) : datasource !== CLUSTER_DEFAULT_FAKE_DATASOURCE ? ( -

- This datasource currently has no rules, it will use the cluster defaults. -

- ) : undefined} -
- +
+
+ + {currentRules.length ? ( + currentRules.map((rule, index) => ( + changeRule(r, index)} + onDelete={() => deleteRule(index)} + moveUp={index > 0 ? () => moveRule(index, -1) : undefined} + moveDown={ + index < currentRules.length - 1 ? () => moveRule(index, 1) : undefined + } + /> + )) + ) : datasource !== CLUSTER_DEFAULT_FAKE_DATASOURCE ? ( +

+ This datasource currently has no rules, it will use the cluster defaults. +

+ ) : undefined} +
+ +
+
+ {defaultRuleRender && } + {defaultRuleRender}
- +
) : ( - - )} - {datasource !== CLUSTER_DEFAULT_FAKE_DATASOURCE && ( <> - - - Cluster defaults (edit) - - } - > -

The cluster default rules are evaluated if none of the above rules match.

- {currentTab === 'form' ? ( - defaultRules.map((rule, index) => ( - - )) - ) : ( - - )} -
+ + {defaultRuleRender} )} From e244d1508c138c8726d98bc89ba59d6594af67f8 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 16 Jul 2024 11:07:06 -0700 Subject: [PATCH 3/5] actions menus should have titles --- .../src/components/action-cell/action-cell.spec.tsx | 2 +- web-console/src/components/action-cell/action-cell.tsx | 7 ++++--- web-console/src/utils/basic-action.tsx | 8 ++++++-- .../src/views/datasources-view/datasources-view.tsx | 1 + web-console/src/views/lookups-view/lookups-view.tsx | 1 + web-console/src/views/segments-view/segments-view.tsx | 1 + web-console/src/views/services-view/services-view.tsx | 2 +- .../src/views/supervisors-view/supervisors-view.tsx | 1 + web-console/src/views/tasks-view/tasks-view.tsx | 1 + 9 files changed, 17 insertions(+), 7 deletions(-) diff --git a/web-console/src/components/action-cell/action-cell.spec.tsx b/web-console/src/components/action-cell/action-cell.spec.tsx index 6349ee1042e6..b52c6af4e847 100644 --- a/web-console/src/components/action-cell/action-cell.spec.tsx +++ b/web-console/src/components/action-cell/action-cell.spec.tsx @@ -23,7 +23,7 @@ import { ActionCell } from './action-cell'; describe('ActionCell', () => { it('matches snapshot', () => { - const actionCell = {}} actions={[]} />; + const actionCell = {}} actions={[]} menuTitle="item" />; const { container } = render(actionCell); expect(container.firstChild).toMatchSnapshot(); }); diff --git a/web-console/src/components/action-cell/action-cell.tsx b/web-console/src/components/action-cell/action-cell.tsx index c0e522e68ab6..4a20e429977e 100644 --- a/web-console/src/components/action-cell/action-cell.tsx +++ b/web-console/src/components/action-cell/action-cell.tsx @@ -34,12 +34,13 @@ export const ACTION_COLUMN_WIDTH = 70; export interface ActionCellProps { onDetail?: () => void; disableDetail?: boolean; - actions?: BasicAction[]; + actions: BasicAction[]; + menuTitle: string; } export const ActionCell = React.memo(function ActionCell(props: ActionCellProps) { - const { onDetail, disableDetail, actions } = props; - const actionsMenu = actions ? basicActionsToMenu(actions) : null; + const { onDetail, disableDetail, actions, menuTitle } = props; + const actionsMenu = basicActionsToMenu(actions, menuTitle); return (
diff --git a/web-console/src/utils/basic-action.tsx b/web-console/src/utils/basic-action.tsx index 7cb591b63225..5194d26fa1b8 100644 --- a/web-console/src/utils/basic-action.tsx +++ b/web-console/src/utils/basic-action.tsx @@ -17,7 +17,7 @@ */ import type { IconName, Intent } from '@blueprintjs/core'; -import { Menu, MenuItem } from '@blueprintjs/core'; +import { Menu, MenuDivider, MenuItem } from '@blueprintjs/core'; import type { JSX } from 'react'; import React from 'react'; @@ -29,10 +29,14 @@ export interface BasicAction { disabledReason?: string; } -export function basicActionsToMenu(basicActions: BasicAction[]): JSX.Element | undefined { +export function basicActionsToMenu( + basicActions: BasicAction[], + title?: string, +): JSX.Element | undefined { if (!basicActions.length) return; return ( + {title && } {basicActions.map(({ icon, title, intent, onAction, disabledReason }, i) => ( ); }, diff --git a/web-console/src/views/lookups-view/lookups-view.tsx b/web-console/src/views/lookups-view/lookups-view.tsx index caeee7d34663..b3df25717fee 100644 --- a/web-console/src/views/lookups-view/lookups-view.tsx +++ b/web-console/src/views/lookups-view/lookups-view.tsx @@ -464,6 +464,7 @@ export class LookupsView extends React.PureComponent ); }, diff --git a/web-console/src/views/segments-view/segments-view.tsx b/web-console/src/views/segments-view/segments-view.tsx index 7d7bcaeec468..c266e7bd9948 100644 --- a/web-console/src/views/segments-view/segments-view.tsx +++ b/web-console/src/views/segments-view/segments-view.tsx @@ -886,6 +886,7 @@ END AS "time_span"`, this.onDetail(id, datasource); }} actions={this.getSegmentActions(id, datasource)} + menuTitle={id} /> ); }, diff --git a/web-console/src/views/services-view/services-view.tsx b/web-console/src/views/services-view/services-view.tsx index 52de53f80de6..86693f85fcf0 100644 --- a/web-console/src/views/services-view/services-view.tsx +++ b/web-console/src/views/services-view/services-view.tsx @@ -656,7 +656,7 @@ ORDER BY const { worker } = value; const disabled = worker.version === ''; const workerActions = this.getWorkerActions(worker.host, disabled); - return ; + return ; }, Aggregated: () => '', }, diff --git a/web-console/src/views/supervisors-view/supervisors-view.tsx b/web-console/src/views/supervisors-view/supervisors-view.tsx index 68654cbce79d..792b16a58ce9 100644 --- a/web-console/src/views/supervisors-view/supervisors-view.tsx +++ b/web-console/src/views/supervisors-view/supervisors-view.tsx @@ -898,6 +898,7 @@ export class SupervisorsView extends React.PureComponent< this.onSupervisorDetail(row.original)} actions={supervisorActions} + menuTitle={id} /> ); }, diff --git a/web-console/src/views/tasks-view/tasks-view.tsx b/web-console/src/views/tasks-view/tasks-view.tsx index 49a66b71cb1c..5e3d59f06a19 100644 --- a/web-console/src/views/tasks-view/tasks-view.tsx +++ b/web-console/src/views/tasks-view/tasks-view.tsx @@ -505,6 +505,7 @@ ORDER BY this.onTaskDetail(row.original)} actions={this.getTaskActions(id, datasource, status, type, true)} + menuTitle={id} /> ); }, From 453218c8649f475528aab810acbc65f9b02e8991 Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 16 Jul 2024 13:48:46 -0700 Subject: [PATCH 4/5] change term --- web-console/src/views/datasources-view/datasources-view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-console/src/views/datasources-view/datasources-view.tsx b/web-console/src/views/datasources-view/datasources-view.tsx index 70d71ec44599..da5f8dbbff16 100644 --- a/web-console/src/views/datasources-view/datasources-view.tsx +++ b/web-console/src/views/datasources-view/datasources-view.tsx @@ -1212,7 +1212,7 @@ GROUP BY 1, 2`; num_segments !== num_zero_replica_segments ? `Fully ${descriptor}` : undefined, - hasZeroReplicationRule ? `${percentZeroReplica}% async only` : '', + hasZeroReplicationRule ? `${percentZeroReplica}% deep storage only` : '', ).join(', ')}{' '} ({segmentsEl}) @@ -1228,7 +1228,7 @@ GROUP BY 1, 2`; {numAvailableSegments ? '\u25cf' : '\u25cb'}  {`${percentAvailable}% ${descriptor}${ - hasZeroReplicationRule ? `, ${percentZeroReplica}% async only` : '' + hasZeroReplicationRule ? `, ${percentZeroReplica}% deep storage only` : '' }`}{' '} ({segmentsEl}) From cfad5e2c6a7c94e3094b07bcafc6183cead897ff Mon Sep 17 00:00:00 2001 From: Vadim Ogievetsky Date: Tue, 16 Jul 2024 21:19:19 -0700 Subject: [PATCH 5/5] correctly name sort/shuffle --- .../src/druid-models/stages/stages.spec.ts | 6 +- web-console/src/druid-models/stages/stages.ts | 59 ++++++++++--------- .../execution-stages-pane.tsx | 13 ++-- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/web-console/src/druid-models/stages/stages.spec.ts b/web-console/src/druid-models/stages/stages.spec.ts index 8bf60d414e96..ad8f2e77408b 100644 --- a/web-console/src/druid-models/stages/stages.spec.ts +++ b/web-console/src/druid-models/stages/stages.spec.ts @@ -27,8 +27,7 @@ describe('Stages', () => { describe('#getByPartitionCountersForStage', () => { it('works for input', () => { - expect(STAGES.getByPartitionCountersForStage(STAGES.stages[2], 'input')) - .toMatchInlineSnapshot(` + expect(STAGES.getByPartitionCountersForStage(STAGES.stages[2], 'in')).toMatchInlineSnapshot(` [ { "index": 0, @@ -45,8 +44,7 @@ describe('Stages', () => { }); it('works for output', () => { - expect(STAGES.getByPartitionCountersForStage(STAGES.stages[2], 'output')) - .toMatchInlineSnapshot(` + expect(STAGES.getByPartitionCountersForStage(STAGES.stages[2], 'out')).toMatchInlineSnapshot(` [ { "index": 0, diff --git a/web-console/src/druid-models/stages/stages.ts b/web-console/src/druid-models/stages/stages.ts index 2f97e7bc96fe..04cf22be8f98 100644 --- a/web-console/src/druid-models/stages/stages.ts +++ b/web-console/src/druid-models/stages/stages.ts @@ -22,8 +22,10 @@ import { deleteKeys, filterMap, oneOf, zeroDivide } from '../../utils'; import type { InputFormat } from '../input-format/input-format'; import type { InputSource } from '../input-source/input-source'; -const SORT_WEIGHT = 0.5; -const READING_INPUT_WITH_SORT_WEIGHT = 1 - SORT_WEIGHT; +const SHUFFLE_WEIGHT = 0.5; +const READING_INPUT_WITH_SHUFFLE_WEIGHT = 1 - SHUFFLE_WEIGHT; + +export type InOut = 'in' | 'out'; export type StageInput = | { @@ -261,13 +263,13 @@ export class Stages { return Stages.stageType(stage) !== 'segmentGenerator'; } - stageHasSort(stage: StageDefinition): boolean { + stageHasShuffle(stage: StageDefinition): boolean { if (!this.stageHasOutput(stage)) return false; - return Boolean(stage.sort); + return Boolean(stage.sort) || this.hasCounterForStage(stage, 'shuffle'); } - stageOutputCounterName(stage: StageDefinition): ChannelCounterName { - return this.stageHasSort(stage) ? 'shuffle' : 'output'; + stageFinalCounterName(stage: StageDefinition): ChannelCounterName { + return this.stageHasShuffle(stage) ? 'shuffle' : 'output'; } overallProgress(): number { @@ -286,12 +288,14 @@ export class Stages { switch (stage.phase) { case 'READING_INPUT': return ( - (this.stageHasSort(stage) ? READING_INPUT_WITH_SORT_WEIGHT : 1) * + (this.stageHasShuffle(stage) ? READING_INPUT_WITH_SHUFFLE_WEIGHT : 1) * this.readingInputPhaseProgress(stage) ); case 'POST_READING': - return READING_INPUT_WITH_SORT_WEIGHT + SORT_WEIGHT * this.postReadingPhaseProgress(stage); + return ( + READING_INPUT_WITH_SHUFFLE_WEIGHT + SHUFFLE_WEIGHT * this.postReadingPhaseProgress(stage) + ); case 'RESULTS_READY': case 'FINISHED': @@ -415,7 +419,7 @@ export class Stages { } getTotalOutputForStage(stage: StageDefinition, field: 'frames' | 'rows' | 'bytes'): number { - return this.getTotalCounterForStage(stage, this.stageOutputCounterName(stage), field); + return this.getTotalCounterForStage(stage, this.stageFinalCounterName(stage), field); } getSortProgressForStage(stage: StageDefinition): number { @@ -445,7 +449,7 @@ export class Stages { const channelCounters = definition.input.map((_, i) => `input${i}` as ChannelCounterName); if (this.stageHasOutput(stage)) channelCounters.push('output'); - if (this.stageHasSort(stage)) channelCounters.push('shuffle'); + if (this.stageHasShuffle(stage)) channelCounters.push('shuffle'); return channelCounters; } @@ -479,9 +483,9 @@ export class Stages { getPartitionChannelCounterNamesForStage( stage: StageDefinition, - type: 'input' | 'output', + inOut: InOut, ): ChannelCounterName[] { - if (type === 'input') { + if (inOut === 'in') { const { input, broadcast } = stage.definition; return filterMap(input, (input, i) => input.type === 'stage' && !broadcast?.includes(i) @@ -489,31 +493,28 @@ export class Stages { : undefined, ); } else { - return [this.stageOutputCounterName(stage)]; + return [this.stageFinalCounterName(stage)]; } } - getByPartitionCountersForStage( - stage: StageDefinition, - type: 'input' | 'output', - ): SimpleWideCounter[] { - const counterNames = this.getPartitionChannelCounterNamesForStage(stage, type); + getByPartitionCountersForStage(stage: StageDefinition, inOut: InOut): SimpleWideCounter[] { + const counterNames = this.getPartitionChannelCounterNamesForStage(stage, inOut); if (!counterNames.length) return []; if (!this.hasCounterForStage(stage, counterNames[0])) return []; const stageCounters = this.getCountersForStage(stage); - const { partitionCount } = stage; - const partitionNumber = - type === 'output' - ? partitionCount - : max(stageCounters, stageCounter => - max(counterNames, counterName => { - const channelCounter = stageCounter[counterName]; - if (channelCounter?.type !== 'channel') return 0; - return channelCounter.rows?.length || 0; - }), - ); + let partitionNumber = max(stageCounters, stageCounter => + max(counterNames, counterName => { + const channelCounter = stageCounter[counterName]; + if (channelCounter?.type !== 'channel') return 0; + return channelCounter.rows?.length || 0; + }), + ); + + if (inOut === 'out') { + partitionNumber = Math.max(partitionNumber || 0, stage.partitionCount || 0); + } if (!partitionNumber) return []; diff --git a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx index 88cefa045ad8..47d85e16f3fe 100644 --- a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx +++ b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.tsx @@ -32,6 +32,7 @@ import type { ClusterBy, CounterName, Execution, + InOut, SegmentGenerationProgressFields, SimpleWideCounter, StageDefinition, @@ -179,9 +180,9 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane( const phaseIsWorking = oneOf(phase, 'NEW', 'READING_INPUT', 'POST_READING'); return (
- {detailedCountersForPartitions(stage, 'input', phase === 'READING_INPUT')} + {detailedCountersForPartitions(stage, 'in', phase === 'READING_INPUT')} {detailedCountersForWorkers(stage)} - {detailedCountersForPartitions(stage, 'output', phaseIsWorking)} + {detailedCountersForPartitions(stage, 'out', phaseIsWorking)}
); } @@ -320,15 +321,15 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane( function detailedCountersForPartitions( stage: StageDefinition, - type: 'input' | 'output', + inOut: InOut, inProgress: boolean, ) { - const wideCounters = stages.getByPartitionCountersForStage(stage, type); + const wideCounters = stages.getByPartitionCountersForStage(stage, inOut); if (!wideCounters.length) return; const counterNames: ChannelCounterName[] = stages.getPartitionChannelCounterNamesForStage( stage, - type, + inOut, ); const bracesRows: Record = {} as any; @@ -349,7 +350,7 @@ export const ExecutionStagesPane = React.memo(function ExecutionStagesPane( showPagination={wideCounters.length > MAX_DETAIL_ROWS} columns={[ { - Header: `${capitalizeFirst(type)} partitions` + (inProgress ? '*' : ''), + Header: `${capitalizeFirst(inOut)} partitions` + (inProgress ? '*' : ''), id: 'partition', accessor: d => d.index, className: 'padded',