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

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

commit 78ad36ba85028d12372fe684e178593c00235d6a
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Fri Apr 19 12:52:06 2024 -0700

    better tabs
---
 .../compaction-history-dialog.tsx                  |   4 +-
 .../kill-datasource-dialog.tsx                     |   7 +-
 .../supervisor-table-action-dialog.tsx             |  14 +-
 .../task-table-action-dialog.tsx                   |  40 ++---
 .../supervisor-status/supervisor-status.ts         |  11 +-
 .../src/views/load-data-view/info-messages.tsx     |   6 +-
 .../src/views/load-data-view/load-data-view.tsx    |  10 +-
 .../src/views/segments-view/segments-view.tsx      |   6 +-
 .../views/supervisors-view/supervisors-view.scss   |   4 +
 .../views/supervisors-view/supervisors-view.tsx    | 171 ++++++++++++++-------
 web-console/src/views/tasks-view/tasks-view.tsx    |   6 +-
 11 files changed, 179 insertions(+), 100 deletions(-)

diff --git 
a/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
 
b/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
index 4cdc916ee74..cb886d0483d 100644
--- 
a/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
+++ 
b/web-console/src/dialogs/compaction-history-dialog/compaction-history-dialog.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Button, Callout, Classes, Code, Dialog, Tab, Tabs } from 
'@blueprintjs/core';
+import { Button, Callout, Classes, Dialog, Tab, Tabs, Tag } from 
'@blueprintjs/core';
 import * as JSONBig from 'json-bigint-native';
 import React, { useState } from 'react';
 
@@ -117,7 +117,7 @@ export const CompactionHistoryDialog = React.memo(function 
CompactionHistoryDial
             </Tabs>
           ) : (
             <div>
-              There is no compaction history for <Code>{datasource}</Code>.
+              There is no compaction history for <Tag 
minimal>{datasource}</Tag>.
             </div>
           )
         ) : historyState.loading ? (
diff --git 
a/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx 
b/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx
index f95a5a5d3b8..dba85268d00 100644
--- a/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx
+++ b/web-console/src/dialogs/kill-datasource-dialog/kill-datasource-dialog.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Code, Intent } from '@blueprintjs/core';
+import { Intent, Tag } from '@blueprintjs/core';
 import React, { useState } from 'react';
 
 import { FormGroupWithInfo, PopoverText } from '../../components';
@@ -74,13 +74,14 @@ export const KillDatasourceDialog = function 
KillDatasourceDialog(
       warningChecks={[
         <>
           I understand that this operation will delete all metadata about the 
unused segments of{' '}
-          <Code>{datasource}</Code> and removes them from deep storage.
+          <Tag minimal>{datasource}</Tag> and removes them from deep storage.
         </>,
         'I understand that this operation cannot be undone.',
       ]}
     >
       <p>
-        Are you sure you want to permanently delete unused segments in 
<Code>{datasource}</Code>?
+        Are you sure you want to permanently delete unused segments in{' '}
+        <Tag minimal>{datasource}</Tag>?
       </p>
       <p>This action is not reversible and the data deleted will be lost.</p>
       <FormGroupWithInfo
diff --git 
a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
 
b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
index 02d9e3c28b8..5e3d9e50028 100644
--- 
a/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
+++ 
b/web-console/src/dialogs/supervisor-table-action-dialog/supervisor-table-action-dialog.tsx
@@ -28,6 +28,8 @@ import { TableActionDialog } from 
'../table-action-dialog/table-action-dialog';
 
 import { SupervisorStatisticsTable } from 
'./supervisor-statistics-table/supervisor-statistics-table';
 
+type SupervisorTableActionDialogTab = 'status' | 'stats' | 'spec' | 'history';
+
 interface SupervisorTableActionDialogProps {
   supervisorId: string;
   actions: BasicAction[];
@@ -38,7 +40,7 @@ export const SupervisorTableActionDialog = 
React.memo(function SupervisorTableAc
   props: SupervisorTableActionDialogProps,
 ) {
   const { supervisorId, actions, onClose } = props;
-  const [activeTab, setActiveTab] = useState('status');
+  const [activeTab, setActiveTab] = 
useState<SupervisorTableActionDialogTab>('status');
 
   const supervisorTableSideButtonMetadata: SideButtonMetaData[] = [
     {
@@ -49,15 +51,15 @@ export const SupervisorTableActionDialog = 
React.memo(function SupervisorTableAc
     },
     {
       icon: 'chart',
-      text: 'Statistics',
+      text: 'Task stats',
       active: activeTab === 'stats',
       onClick: () => setActiveTab('stats'),
     },
     {
       icon: 'align-left',
-      text: 'Payload',
-      active: activeTab === 'payload',
-      onClick: () => setActiveTab('payload'),
+      text: 'Spec',
+      active: activeTab === 'spec',
+      onClick: () => setActiveTab('spec'),
     },
     {
       icon: 'history',
@@ -88,7 +90,7 @@ export const SupervisorTableActionDialog = 
React.memo(function SupervisorTableAc
           downloadFilename={`supervisor-stats-${supervisorId}.json`}
         />
       )}
-      {activeTab === 'payload' && (
+      {activeTab === 'spec' && (
         <ShowJson
           endpoint={supervisorEndpointBase}
           transform={x => cleanSpec(x, true)}
diff --git 
a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx 
b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
index a0a5dbbf13f..9edc5d996f4 100644
--- 
a/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
+++ 
b/web-console/src/dialogs/task-table-action-dialog/task-table-action-dialog.tsx
@@ -25,18 +25,20 @@ import type { BasicAction } from '../../utils/basic-action';
 import type { SideButtonMetaData } from 
'../table-action-dialog/table-action-dialog';
 import { TableActionDialog } from '../table-action-dialog/table-action-dialog';
 
+type TaskTableActionDialogTab = 'status' | 'report' | 'spec' | 'log';
+
 interface TaskTableActionDialogProps {
   taskId: string;
   actions: BasicAction[];
-  onClose: () => void;
   status: string;
+  onClose(): void;
 }
 
 export const TaskTableActionDialog = React.memo(function TaskTableActionDialog(
   props: TaskTableActionDialogProps,
 ) {
   const { taskId, actions, onClose, status } = props;
-  const [activeTab, setActiveTab] = useState('status');
+  const [activeTab, setActiveTab] = 
useState<TaskTableActionDialogTab>('status');
 
   const taskTableSideButtonMetadata: SideButtonMetaData[] = [
     {
@@ -45,21 +47,21 @@ export const TaskTableActionDialog = React.memo(function 
TaskTableActionDialog(
       active: activeTab === 'status',
       onClick: () => setActiveTab('status'),
     },
-    {
-      icon: 'align-left',
-      text: 'Payload',
-      active: activeTab === 'payload',
-      onClick: () => setActiveTab('payload'),
-    },
     {
       icon: 'comparison',
       text: 'Reports',
-      active: activeTab === 'reports',
-      onClick: () => setActiveTab('reports'),
+      active: activeTab === 'report',
+      onClick: () => setActiveTab('report'),
+    },
+    {
+      icon: 'align-left',
+      text: 'Spec',
+      active: activeTab === 'spec',
+      onClick: () => setActiveTab('spec'),
     },
     {
       icon: 'align-justify',
-      text: 'Logs',
+      text: 'Log',
       active: activeTab === 'log',
       onClick: () => setActiveTab('log'),
     },
@@ -80,20 +82,20 @@ export const TaskTableActionDialog = React.memo(function 
TaskTableActionDialog(
           downloadFilename={`task-status-${taskId}.json`}
         />
       )}
-      {activeTab === 'payload' && (
-        <ShowJson
-          endpoint={taskEndpointBase}
-          transform={x => deepGet(x, 'payload') || x}
-          downloadFilename={`task-payload-${taskId}.json`}
-        />
-      )}
-      {activeTab === 'reports' && (
+      {activeTab === 'report' && (
         <ShowJson
           endpoint={`${taskEndpointBase}/reports`}
           transform={x => deepGet(x, 'ingestionStatsAndErrors.payload') || x}
           downloadFilename={`task-reports-${taskId}.json`}
         />
       )}
+      {activeTab === 'spec' && (
+        <ShowJson
+          endpoint={taskEndpointBase}
+          transform={x => deepGet(x, 'payload') || x}
+          downloadFilename={`task-payload-${taskId}.json`}
+        />
+      )}
       {activeTab === 'log' && (
         <ShowLog
           tail={status === 'RUNNING'}
diff --git 
a/web-console/src/druid-models/supervisor-status/supervisor-status.ts 
b/web-console/src/druid-models/supervisor-status/supervisor-status.ts
index bdbc3187736..d76ad19cc14 100644
--- a/web-console/src/druid-models/supervisor-status/supervisor-status.ts
+++ b/web-console/src/druid-models/supervisor-status/supervisor-status.ts
@@ -42,20 +42,27 @@ export interface SupervisorStatus {
     healthy: boolean;
     state: string;
     detailedState: string;
-    recentErrors: any[];
+    recentErrors: SupervisorError[];
   };
 }
 
 export interface SupervisorStatusTask {
   id: string;
   startingOffsets: SupervisorOffsetMap;
-  startTime: '2024-04-12T21:35:34.834Z';
+  startTime: string;
   remainingSeconds: number;
   type: string;
   currentOffsets: SupervisorOffsetMap;
   lag: SupervisorOffsetMap;
 }
 
+export interface SupervisorError {
+  timestamp: string;
+  exceptionClass: string;
+  message: string;
+  streamException: boolean;
+}
+
 export type SupervisorStats = Record<string, Record<string, RowStats>>;
 
 export type RowStatsKey = 'totals' | '1m' | '5m' | '15m';
diff --git a/web-console/src/views/load-data-view/info-messages.tsx 
b/web-console/src/views/load-data-view/info-messages.tsx
index b88cf8a70c2..ad9e96667db 100644
--- a/web-console/src/views/load-data-view/info-messages.tsx
+++ b/web-console/src/views/load-data-view/info-messages.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Button, Callout, Code, FormGroup, Intent } from '@blueprintjs/core';
+import { Button, Callout, Code, FormGroup, Intent, Tag } from 
'@blueprintjs/core';
 import React from 'react';
 
 import { ExternalLink, LearnMore } from '../../components';
@@ -236,8 +236,8 @@ export const AppendToExistingIssue = React.memo(function 
AppendToExistingIssue(
     <FormGroup>
       <Callout intent={Intent.DANGER}>
         <p>
-          Only <Code>dynamic</Code> partitioning supports 
<Code>appendToExisting: true</Code>. You
-          have currently selected <Code>{partitionsSpecType}</Code> 
partitioning.
+          Only <Tag minimal>dynamic</Tag> partitioning supports 
<Code>appendToExisting: true</Code>.
+          You have currently selected <Tag minimal>{partitionsSpecType}</Tag> 
partitioning.
         </p>
         <Button
           intent={Intent.SUCCESS}
diff --git a/web-console/src/views/load-data-view/load-data-view.tsx 
b/web-console/src/views/load-data-view/load-data-view.tsx
index fdb45420e0a..d95943b4f1c 100644
--- a/web-console/src/views/load-data-view/load-data-view.tsx
+++ b/web-console/src/views/load-data-view/load-data-view.tsx
@@ -34,6 +34,7 @@ import {
   Radio,
   RadioGroup,
   Switch,
+  Tag,
   TextArea,
 } from '@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
@@ -3073,8 +3074,9 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
               <p>Your partitioning and sorting configuration is uncommon.</p>
               <p>
                 For best performance the first dimension in your schema (
-                <Code>{firstDimensionName}</Code>), which is what the data 
will be primarily sorted
-                on, commonly matches the partitioning dimension 
(<Code>{partitionDimension}</Code>).
+                <Tag minimal>{firstDimensionName}</Tag>), which is what the 
data will be primarily
+                sorted on, commonly matches the partitioning dimension (
+                <Tag minimal>{partitionDimension}</Tag>).
               </p>
               <p>
                 <Button
@@ -3452,11 +3454,11 @@ export class LoadDataView extends 
React.PureComponent<LoadDataViewProps, LoadDat
                   <p>
                     You have enabled type-aware schema discovery (
                     <Code>useSchemaDiscovery: true</Code>) to ingest data into 
the existing
-                    datasource <Code>{datasource}</Code>.
+                    datasource <Tag minimal>{datasource}</Tag>.
                   </p>
                   <p>
                     If you used string-based schema discovery when first 
ingesting data to{' '}
-                    <Code>{datasource}</Code>, using type-aware schema 
discovery now can cause
+                    <Tag minimal>{datasource}</Tag>, using type-aware schema 
discovery now can cause
                     problems with the values multi-value string dimensions.
                   </p>
                   <p>
diff --git a/web-console/src/views/segments-view/segments-view.tsx 
b/web-console/src/views/segments-view/segments-view.tsx
index 44d59f6fa95..ba4b84b76c4 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Button, ButtonGroup, Code, Intent, Label, MenuItem, Switch } from 
'@blueprintjs/core';
+import { Button, ButtonGroup, Intent, Label, MenuItem, Switch, Tag } from 
'@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
 import { C, L, SqlComparison, SqlExpression } from '@druid-toolkit/query';
 import classNames from 'classnames';
@@ -919,7 +919,7 @@ END AS "time_span"`,
           );
           return resp.data;
         }}
-        confirmButtonText="Drop Segment"
+        confirmButtonText="Drop segment"
         successText="Segment drop request acknowledged, next time the 
coordinator runs segment will be dropped"
         failText="Could not drop segment"
         intent={Intent.DANGER}
@@ -931,7 +931,7 @@ END AS "time_span"`,
         }}
       >
         <p>
-          Are you sure you want to drop segment 
<Code>{terminateSegmentId}</Code>?
+          Are you sure you want to drop segment <Tag 
minimal>{terminateSegmentId}</Tag>?
         </p>
         <p>This action is not reversible.</p>
       </AsyncActionDialog>
diff --git a/web-console/src/views/supervisors-view/supervisors-view.scss 
b/web-console/src/views/supervisors-view/supervisors-view.scss
index edf04bc4e0d..6db2b646d95 100644
--- a/web-console/src/views/supervisors-view/supervisors-view.scss
+++ b/web-console/src/views/supervisors-view/supervisors-view.scss
@@ -28,5 +28,9 @@
     top: $view-control-bar-height + $standard-padding;
     bottom: 0;
     width: 100%;
+
+    .title-button {
+      cursor: pointer;
+    }
   }
 }
diff --git a/web-console/src/views/supervisors-view/supervisors-view.tsx 
b/web-console/src/views/supervisors-view/supervisors-view.tsx
index 6e872792220..0483cbd2184 100644
--- a/web-console/src/views/supervisors-view/supervisors-view.tsx
+++ b/web-console/src/views/supervisors-view/supervisors-view.tsx
@@ -16,9 +16,11 @@
  * limitations under the License.
  */
 
-import { Button, Code, Intent, Menu, MenuItem, Position } from 
'@blueprintjs/core';
+import { Icon, Intent, Menu, MenuItem, Position, Tag } from 
'@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
 import { Popover2 } from '@blueprintjs/popover2';
+import * as JSONBig from 'json-bigint-native';
+import type { JSX } from 'react';
 import React from 'react';
 import type { Filter } from 'react-table';
 import ReactTable from 'react-table';
@@ -42,7 +44,12 @@ import {
   SupervisorTableActionDialog,
 } from '../../dialogs';
 import { SupervisorResetOffsetsDialog } from 
'../../dialogs/supervisor-reset-offsets-dialog/supervisor-reset-offsets-dialog';
-import type { QueryWithContext, RowStatsKey, SupervisorStatus } from 
'../../druid-models';
+import type {
+  IngestionSpec,
+  QueryWithContext,
+  RowStatsKey,
+  SupervisorStatus,
+} from '../../druid-models';
 import { getTotalSupervisorStats } from '../../druid-models';
 import type { Capabilities } from '../../helpers';
 import {
@@ -64,6 +71,7 @@ import {
   hasPopoverOpen,
   LocalStorageBackedVisibility,
   LocalStorageKeys,
+  nonEmptyArray,
   oneOf,
   pluralIfNeeded,
   queryDruidSql,
@@ -81,16 +89,16 @@ const supervisorTableColumns: string[] = [
   'Type',
   'Topic/Stream',
   'Status',
-  'Active tasks',
+  'Configured tasks',
+  'Running tasks',
   'Aggregate lag',
   'Stats',
   ACTION_COLUMN_LABEL,
 ];
 
-const ROW_STATS_KEYS: RowStatsKey[] = ['totals', '1m', '5m', '15m'];
+const ROW_STATS_KEYS: RowStatsKey[] = ['1m', '5m', '15m'];
 
 function getRowStatsKeyTitle(key: RowStatsKey) {
-  if (key === 'totals') return 'Total';
   return `Rate over past ${pluralIfNeeded(parseInt(key, 10), 'minute')}`;
 }
 
@@ -104,6 +112,7 @@ interface SupervisorQueryResultRow {
   type: string;
   source: string;
   detailed_state: string;
+  spec?: IngestionSpec;
   suspended: boolean;
   status?: SupervisorStatus;
   stats?: any;
@@ -182,14 +191,6 @@ export class SupervisorsView extends React.PureComponent<
     SupervisorQueryResultRow[]
   >;
 
-  static SUPERVISOR_SQL_BASE = `WITH s AS (SELECT
-  "supervisor_id",
-  "type",
-  "source",
-  CASE WHEN "suspended" = 0 THEN "detailed_state" ELSE 'SUSPENDED' END AS 
"detailed_state",
-  "suspended" = 1 AS "suspended"
-FROM "sys"."supervisors")`;
-
   constructor(props: SupervisorsViewProps) {
     super(props);
 
@@ -219,7 +220,14 @@ FROM "sys"."supervisors")`;
         let supervisors: SupervisorQueryResultRow[];
         if (capabilities.hasSql()) {
           const sqlQuery = assemble(
-            SupervisorsView.SUPERVISOR_SQL_BASE,
+            'WITH s AS (SELECT',
+            '  "supervisor_id",',
+            '  "type",',
+            '  "source",',
+            `  CASE WHEN "suspended" = 0 THEN "detailed_state" ELSE 
'SUSPENDED' END AS "detailed_state",`,
+            visibleColumns.shown('Configured tasks') ? '  "spec",' : undefined,
+            '  "suspended" = 1 AS "suspended"',
+            'FROM "sys"."supervisors")',
             'SELECT *',
             'FROM s',
             filtered.length
@@ -236,6 +244,13 @@ FROM "sys"."supervisors")`;
             },
             cancelToken,
           );
+
+          for (const supervisor of supervisors) {
+            const spec: any = supervisor.spec;
+            if (typeof spec === 'string') {
+              supervisor.spec = JSONBig.parse(spec);
+            }
+          }
         } else if (capabilities.hasOverlordAccess()) {
           const supervisorList = (
             await Api.instance.get('/druid/indexer/v1/supervisor?full', { 
cancelToken })
@@ -253,6 +268,7 @@ FROM "sys"."supervisors")`;
                 'n/a',
               state: deepGet(sup, 'state'),
               detailed_state: deepGet(sup, 'detailedState'),
+              spec: sup.spec,
               suspended: Boolean(deepGet(sup, 'suspended')),
             };
           });
@@ -272,7 +288,7 @@ FROM "sys"."supervisors")`;
         }
 
         if (capabilities.hasOverlordAccess()) {
-          if (visibleColumns.shown('Active tasks') || 
visibleColumns.shown('Aggregate lag')) {
+          if (visibleColumns.shown('Running tasks') || 
visibleColumns.shown('Aggregate lag')) {
             try {
               for (const supervisor of supervisors) {
                 cancelToken.throwIfRequested();
@@ -451,7 +467,7 @@ FROM "sys"."supervisors")`;
         }}
       >
         <p>
-          Are you sure you want to resume supervisor 
<Code>{resumeSupervisorId}</Code>?
+          Are you sure you want to resume supervisor <Tag 
minimal>{resumeSupervisorId}</Tag>?
         </p>
       </AsyncActionDialog>
     );
@@ -482,7 +498,7 @@ FROM "sys"."supervisors")`;
         }}
       >
         <p>
-          Are you sure you want to suspend supervisor 
<Code>{suspendSupervisorId}</Code>?
+          Are you sure you want to suspend supervisor <Tag 
minimal>{suspendSupervisorId}</Tag>?
         </p>
       </AsyncActionDialog>
     );
@@ -527,17 +543,20 @@ FROM "sys"."supervisors")`;
           this.supervisorQueryManager.rerunLastQuery();
         }}
         warningChecks={[
-          `I understand that resetting ${resetSupervisorId} will clear 
checkpoints and therefore lead to data loss or duplication.`,
+          <>
+            I understand that resetting <Tag minimal>{resetSupervisorId}</Tag> 
will clear
+            checkpoints and may lead to data loss or duplication.
+          </>,
           'I understand that this operation cannot be undone.',
         ]}
       >
         <p>
-          Are you sure you want to hard reset supervisor 
<Code>{resetSupervisorId}</Code>?
+          Are you sure you want to hard reset supervisor <Tag 
minimal>{resetSupervisorId}</Tag>?
         </p>
-        <p>Hard resetting a supervisor will lead to data loss or data 
duplication.</p>
+        <p>Hard resetting a supervisor may lead to data loss or data 
duplication.</p>
         <p>
-          The reason for using this operation is to recover from a state in 
which the supervisor
-          ceases operating due to missing offsets.
+          Use this operation to restore functionality when the supervisor 
stops operating due to
+          missing offsets.
         </p>
       </AsyncActionDialog>
     );
@@ -568,7 +587,7 @@ FROM "sys"."supervisors")`;
         }}
       >
         <p>
-          Are you sure you want to terminate supervisor 
<Code>{terminateSupervisorId}</Code>?
+          Are you sure you want to terminate supervisor <Tag 
minimal>{terminateSupervisorId}</Tag>?
         </p>
         <p>This action is not reversible.</p>
       </AsyncActionDialog>
@@ -673,29 +692,69 @@ FROM "sys"."supervisors")`;
             show: visibleColumns.shown('Status'),
           },
           {
-            Header: 'Active tasks',
-            id: 'active_tasks',
+            Header: 'Configured tasks',
+            id: 'configured_tasks',
             width: 150,
-            accessor: 'status.payload.activeTasks',
+            accessor: 'spec',
+            filterable: false,
+            sortable: false,
+            className: 'padded',
+            Cell: ({ value }) => {
+              if (!value) return null;
+              const taskCount = deepGet(value, 'spec.ioConfig.taskCount');
+              const replicas = deepGet(value, 'spec.ioConfig.replicas');
+              if (typeof taskCount !== 'number' || typeof replicas !== 
'number') return null;
+              return (
+                <div>
+                  <div>{formatInteger(taskCount * replicas)}</div>
+                  <div>
+                    {replicas === 1
+                      ? '(no replication)'
+                      : `(${pluralIfNeeded(taskCount, 'task')} × 
${pluralIfNeeded(
+                          replicas,
+                          'replica',
+                        )})`}
+                  </div>
+                </div>
+              );
+            },
+            show: visibleColumns.shown('Configured tasks'),
+          },
+          {
+            Header: 'Running tasks',
+            id: 'running_tasks',
+            width: 150,
+            accessor: 'status.payload',
             filterable: false,
             sortable: false,
             Cell: ({ value, original }) => {
               if (original.suspended) return;
+              let label: string | JSX.Element;
+              const { activeTasks, publishingTasks } = value || {};
+              if (Array.isArray(activeTasks)) {
+                label = pluralIfNeeded(activeTasks.length, 'active task');
+                if (nonEmptyArray(publishingTasks)) {
+                  label = (
+                    <>
+                      <div>{label}</div>
+                      <div>{pluralIfNeeded(publishingTasks.length, 'publishing 
task')}</div>
+                    </>
+                  );
+                }
+              } else {
+                label = 'n/a';
+              }
               return (
                 <TableClickableCell
                   onClick={() => goToTasks(original.supervisor_id, 
`index_${original.type}`)}
                   hoverIcon={IconNames.ARROW_TOP_RIGHT}
                   title="Go to tasks"
                 >
-                  {typeof value === 'undefined'
-                    ? 'n/a'
-                    : value.length > 0
-                    ? pluralIfNeeded(value.length, 'running task')
-                    : `No running tasks`}
+                  {label}
                 </TableClickableCell>
               );
             },
-            show: visibleColumns.shown('Active tasks'),
+            show: visibleColumns.shown('Running tasks'),
           },
           {
             Header: 'Aggregate lag',
@@ -708,7 +767,30 @@ FROM "sys"."supervisors")`;
             Cell: ({ value }) => formatInteger(value),
           },
           {
-            Header: twoLines('Stats', <i>{getRowStatsKeyTitle(statsKey)}</i>),
+            Header: twoLines(
+              'Stats',
+              <Popover2
+                position={Position.BOTTOM}
+                content={
+                  <Menu>
+                    {ROW_STATS_KEYS.map(k => (
+                      <MenuItem
+                        key={k}
+                        icon={checkedCircleIcon(k === statsKey)}
+                        text={getRowStatsKeyTitle(k)}
+                        onClick={() => {
+                          this.setState({ statsKey: k });
+                        }}
+                      />
+                    ))}
+                  </Menu>
+                }
+              >
+                <i className="title-button">
+                  {getRowStatsKeyTitle(statsKey)} <Icon 
icon={IconNames.CARET_DOWN} />
+                </i>
+              </Popover2>,
+            ),
             id: 'stats',
             width: 220,
             filterable: false,
@@ -889,7 +971,6 @@ FROM "sys"."supervisors")`;
       supervisorTableActionDialogId,
       supervisorTableActionDialogActions,
       visibleColumns,
-      statsKey,
     } = this.state;
 
     return (
@@ -902,28 +983,6 @@ FROM "sys"."supervisors")`;
               this.supervisorQueryManager.rerunLastQuery(auto);
             }}
           />
-          <Popover2
-            position={Position.BOTTOM}
-            content={
-              <Menu>
-                {ROW_STATS_KEYS.map(k => (
-                  <MenuItem
-                    key={k}
-                    icon={checkedCircleIcon(k === statsKey)}
-                    text={getRowStatsKeyTitle(k)}
-                    onClick={() => {
-                      this.setState({ statsKey: k });
-                    }}
-                  />
-                ))}
-              </Menu>
-            }
-          >
-            <Button
-              text={`Stats: ${getRowStatsKeyTitle(statsKey)}`}
-              rightIcon={IconNames.CARET_DOWN}
-            />
-          </Popover2>
           {this.renderBulkSupervisorActions()}
           <TableColumnSelector
             columns={supervisorTableColumns}
diff --git a/web-console/src/views/tasks-view/tasks-view.tsx 
b/web-console/src/views/tasks-view/tasks-view.tsx
index 795c9908412..2be3575b9e1 100644
--- a/web-console/src/views/tasks-view/tasks-view.tsx
+++ b/web-console/src/views/tasks-view/tasks-view.tsx
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-import { Button, ButtonGroup, Intent, Label, MenuItem } from 
'@blueprintjs/core';
+import { Button, ButtonGroup, Intent, Label, MenuItem, Tag } from 
'@blueprintjs/core';
 import { IconNames } from '@blueprintjs/icons';
 import React from 'react';
 import type { Filter } from 'react-table';
@@ -310,7 +310,9 @@ ORDER BY
           this.taskQueryManager.rerunLastQuery();
         }}
       >
-        <p>{`Are you sure you want to kill task '${killTaskId}'?`}</p>
+        <p>
+          Are you sure you want to kill task <Tag minimal>{killTaskId}</Tag>?
+        </p>
       </AsyncActionDialog>
     );
   }


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

Reply via email to