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

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


The following commit(s) were added to refs/heads/master by this push:
     new e23abc710a Web console: default max workers to cluster capacity and 
simplify live reports (#13577)
e23abc710a is described below

commit e23abc710aa2882be9d350b97d2290b8e163691c
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Fri Dec 16 15:13:32 2022 -0800

    Web console: default max workers to cluster capacity and simplify live 
reports (#13577)
    
    * step
    
    * better capacity
    
    * start with capacity
    
    * more compressed stats display
    
    * better rule editor
    
    * fix SQL data loader also
    
    * update snapshots
    
    * new line
    
    * better formatting
---
 .../src/components/braced-text/braced-text.tsx     |   6 +-
 .../__snapshots__/header-bar.spec.tsx.snap         |   1 +
 .../src/components/header-bar/header-bar.spec.tsx  |   2 +-
 .../src/components/header-bar/header-bar.tsx       |   2 +-
 .../__snapshots__/rule-editor.spec.tsx.snap        | 192 +++++++++++++++++++--
 .../components/rule-editor/rule-editor.spec.tsx    |  10 +-
 .../src/components/rule-editor/rule-editor.tsx     |   2 +-
 .../segment-timeline/segment-timeline.spec.tsx     |   2 +-
 .../segment-timeline/segment-timeline.tsx          |   2 +-
 web-console/src/console-application.tsx            |  25 ++-
 .../__snapshots__/retention-dialog.spec.tsx.snap   |   6 +-
 .../src/druid-models/execution/execution.ts        |   4 +-
 .../druid-models/query-context/query-context.tsx   |   5 +-
 .../workbench-query/workbench-query.ts             |   7 +
 web-console/src/{utils => helpers}/capabilities.ts |  48 ++++--
 web-console/src/helpers/capacity.ts                |   6 +-
 web-console/src/helpers/index.ts                   |   1 +
 web-console/src/hooks/index.ts                     |   1 +
 .../segments-card.spec.tsx => hooks/use-clock.ts}  |  30 ++--
 web-console/src/utils/index.tsx                    |   1 -
 web-console/src/utils/load-rule.ts                 |  32 +++-
 .../datasources-view/datasources-view.spec.tsx     |   2 +-
 .../views/datasources-view/datasources-view.tsx    |  55 +++---
 .../__snapshots__/home-view.spec.tsx.snap          |  12 ++
 .../datasources-card/datasources-card.spec.tsx     |   2 +-
 .../datasources-card/datasources-card.tsx          |   5 +-
 .../home-view/home-view-card/home-view-card.scss   |   2 +-
 web-console/src/views/home-view/home-view.spec.tsx |   2 +-
 web-console/src/views/home-view/home-view.tsx      |   2 +-
 .../home-view/lookups-card/lookups-card.spec.tsx   |   2 +-
 .../views/home-view/lookups-card/lookups-card.tsx  |   3 +-
 .../home-view/segments-card/segments-card.spec.tsx |   2 +-
 .../home-view/segments-card/segments-card.tsx      |   3 +-
 .../home-view/services-card/services-card.spec.tsx |   2 +-
 .../home-view/services-card/services-card.tsx      |   3 +-
 .../supervisors-card/supervisors-card.spec.tsx     |   2 +-
 .../supervisors-card/supervisors-card.tsx          |   3 +-
 .../views/home-view/tasks-card/tasks-card.spec.tsx |   2 +-
 .../src/views/home-view/tasks-card/tasks-card.tsx  | 112 ++++++------
 .../views/ingestion-view/ingestion-view.spec.tsx   |   2 +-
 .../src/views/ingestion-view/ingestion-view.tsx    |   2 +-
 .../__snapshots__/segments-view.spec.tsx.snap      |  28 ++-
 .../src/views/segments-view/segments-view.spec.tsx |   2 +-
 .../src/views/segments-view/segments-view.tsx      |  49 ++++--
 .../src/views/services-view/services-view.spec.tsx |   3 +-
 .../src/views/services-view/services-view.tsx      |   3 +-
 .../ingestion-progress-dialog.tsx                  |  25 ++-
 .../sql-data-loader-view/sql-data-loader-view.tsx  |  73 ++++++--
 .../capacity-alert/capacity-alert.tsx              |   6 +-
 .../execution-stages-pane.spec.tsx.snap            |  16 +-
 .../execution-stages-pane.scss                     |   5 -
 .../execution-stages-pane.tsx                      | 160 ++++++++---------
 .../workbench-view/helper-query/helper-query.tsx   |  12 +-
 .../__snapshots__/max-tasks-button.spec.tsx.snap   |  83 ++-------
 .../max-tasks-button/max-tasks-button.spec.tsx     |   4 +-
 .../max-tasks-button/max-tasks-button.tsx          |  38 +++-
 .../views/workbench-view/query-tab/query-tab.tsx   |  13 +-
 .../recent-query-task-panel.tsx                    |  11 +-
 .../views/workbench-view/run-panel/run-panel.tsx   |  10 +-
 .../src/views/workbench-view/workbench-view.tsx    |  12 +-
 60 files changed, 736 insertions(+), 422 deletions(-)

diff --git a/web-console/src/components/braced-text/braced-text.tsx 
b/web-console/src/components/braced-text/braced-text.tsx
index 7416653d98..59840215ce 100644
--- a/web-console/src/components/braced-text/braced-text.tsx
+++ b/web-console/src/components/braced-text/braced-text.tsx
@@ -30,6 +30,7 @@ export interface BracedTextProps {
   braces: string[];
   padFractionalPart?: boolean;
   unselectableThousandsSeparator?: boolean;
+  title?: string;
 }
 
 export function findMostNumbers(strings: string[]): string {
@@ -82,7 +83,8 @@ function hideThousandsSeparator(text: string) {
 }
 
 export const BracedText = React.memo(function BracedText(props: 
BracedTextProps) {
-  const { className, text, braces, padFractionalPart, 
unselectableThousandsSeparator } = props;
+  const { className, text, braces, padFractionalPart, 
unselectableThousandsSeparator, title } =
+    props;
 
   let effectiveBraces = braces.concat(text);
 
@@ -112,7 +114,7 @@ export const BracedText = React.memo(function 
BracedText(props: BracedTextProps)
   }
 
   return (
-    <span className={classNames('braced-text', className)}>
+    <span className={classNames('braced-text', className)} title={title}>
       <span className="brace-text">{findMostNumbers(effectiveBraces)}</span>
       <span className="real-text">
         {unselectableThousandsSeparator ? hideThousandsSeparator(text) : text}
diff --git 
a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap 
b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
index 92ea6c77b8..43415e5a30 100644
--- 
a/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
+++ 
b/web-console/src/components/header-bar/__snapshots__/header-bar.spec.tsx.snap
@@ -181,6 +181,7 @@ exports[`HeaderBar matches snapshot 1`] = `
     <Memo(RestrictedMode)
       capabilities={
         Capabilities {
+          "clusterCapacity": undefined,
           "coordinator": true,
           "multiStageQuery": true,
           "overlord": true,
diff --git a/web-console/src/components/header-bar/header-bar.spec.tsx 
b/web-console/src/components/header-bar/header-bar.spec.tsx
index 2e17be7058..a185110436 100644
--- a/web-console/src/components/header-bar/header-bar.spec.tsx
+++ b/web-console/src/components/header-bar/header-bar.spec.tsx
@@ -19,7 +19,7 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 
 import { HeaderBar } from './header-bar';
 
diff --git a/web-console/src/components/header-bar/header-bar.tsx 
b/web-console/src/components/header-bar/header-bar.tsx
index d7d2b15263..5df62f6776 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -40,9 +40,9 @@ import {
   DoctorDialog,
   OverlordDynamicConfigDialog,
 } from '../../dialogs';
+import { Capabilities } from '../../helpers';
 import { getLink } from '../../links';
 import {
-  Capabilities,
   localStorageGetJson,
   LocalStorageKeys,
   localStorageRemove,
diff --git 
a/web-console/src/components/rule-editor/__snapshots__/rule-editor.spec.tsx.snap
 
b/web-console/src/components/rule-editor/__snapshots__/rule-editor.spec.tsx.snap
index 4c54888d9e..fc39acf142 100644
--- 
a/web-console/src/components/rule-editor/__snapshots__/rule-editor.spec.tsx.snap
+++ 
b/web-console/src/components/rule-editor/__snapshots__/rule-editor.spec.tsx.snap
@@ -14,7 +14,7 @@ exports[`RuleEditor matches snapshot no tier in rule 1`] = `
       <span
         class="bp4-button-text"
       >
-        loadForever
+        loadForever(1x)
       </span>
       <span
         aria-hidden="true"
@@ -166,6 +166,170 @@ exports[`RuleEditor matches snapshot no tier in rule 1`] 
= `
           <div
             class="bp4-form-content"
           >
+            <div
+              class="bp4-control-group"
+            >
+              <button
+                class="bp4-button bp4-minimal"
+                style="pointer-events: none;"
+                type="button"
+              >
+                <span
+                  class="bp4-button-text"
+                >
+                  Replicants:
+                </span>
+              </button>
+              <div
+                class="bp4-control-group bp4-numeric-input"
+              >
+                <div
+                  class="bp4-input-group"
+                >
+                  <input
+                    autocomplete="off"
+                    class="bp4-input"
+                    max="256"
+                    min="0"
+                    type="text"
+                    value="1"
+                  />
+                </div>
+                <div
+                  class="bp4-button-group bp4-vertical bp4-fixed"
+                >
+                  <button
+                    aria-label="increment"
+                    class="bp4-button"
+                    type="button"
+                  >
+                    <span
+                      aria-hidden="true"
+                      class="bp4-icon bp4-icon-chevron-up"
+                      icon="chevron-up"
+                      tabindex="0"
+                    >
+                      <svg
+                        data-icon="chevron-up"
+                        height="16"
+                        viewBox="0 0 16 16"
+                        width="16"
+                      >
+                        <path
+                          d="M12.71 9.29l-4-4C8.53 5.11 8.28 5 8 
5s-.53.11-.71.29l-4 4a1.003 1.003 0 001.42 1.42L8 7.41l3.29 
3.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71z"
+                          fill-rule="evenodd"
+                        />
+                      </svg>
+                    </span>
+                  </button>
+                  <button
+                    aria-label="decrement"
+                    class="bp4-button"
+                    type="button"
+                  >
+                    <span
+                      aria-hidden="true"
+                      class="bp4-icon bp4-icon-chevron-down"
+                      icon="chevron-down"
+                      tabindex="0"
+                    >
+                      <svg
+                        data-icon="chevron-down"
+                        height="16"
+                        viewBox="0 0 16 16"
+                        width="16"
+                      >
+                        <path
+                          d="M12 5c-.28 0-.53.11-.71.29L8 8.59l-3.29-3.3a1.003 
1.003 0 00-1.42 1.42l4 4c.18.18.43.29.71.29s.53-.11.71-.29l4-4A1.003 1.003 0 
0012 5z"
+                          fill-rule="evenodd"
+                        />
+                      </svg>
+                    </span>
+                  </button>
+                </div>
+              </div>
+              <button
+                class="bp4-button bp4-minimal"
+                style="pointer-events: none;"
+                type="button"
+              >
+                <span
+                  class="bp4-button-text"
+                >
+                  Tier:
+                </span>
+              </button>
+              <div
+                class="bp4-html-select bp4-fill"
+              >
+                <select>
+                  <option
+                    value="test1"
+                  >
+                    test1
+                  </option>
+                  <option
+                    value="test"
+                  >
+                    test
+                  </option>
+                  <option
+                    value="test"
+                  >
+                    test
+                  </option>
+                  <option
+                    value="test"
+                  >
+                    test
+                  </option>
+                </select>
+                <span
+                  class="bp4-icon bp4-icon-double-caret-vertical"
+                  icon="double-caret-vertical"
+                >
+                  <svg
+                    data-icon="double-caret-vertical"
+                    height="16"
+                    viewBox="0 0 16 16"
+                    width="16"
+                  >
+                    <desc>
+                      Open dropdown
+                    </desc>
+                    <path
+                      d="M5 7h6a1.003 1.003 0 00.71-1.71l-3-3C8.53 2.11 8.28 2 
8 2s-.53.11-.71.29l-3 3A1.003 1.003 0 005 7zm6 2H5a1.003 1.003 0 00-.71 1.71l3 
3c.18.18.43.29.71.29s.53-.11.71-.29l3-3A1.003 1.003 0 0011 9z"
+                      fill-rule="evenodd"
+                    />
+                  </svg>
+                </span>
+              </div>
+              <button
+                class="bp4-button bp4-disabled"
+                disabled=""
+                tabindex="-1"
+                type="button"
+              >
+                <span
+                  aria-hidden="true"
+                  class="bp4-icon bp4-icon-trash"
+                  icon="trash"
+                  tabindex="0"
+                >
+                  <svg
+                    data-icon="trash"
+                    height="16"
+                    viewBox="0 0 16 16"
+                    width="16"
+                  >
+                    <path
+                      d="M14.49 3.99h-13c-.28 0-.5.22-.5.5s.22.5.5.5h.5v10c0 
.55.45 1 1 1h10c.55 0 1-.45 1-1v-10h.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5zm-8.5 
9c0 .55-.45 1-1 1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm3 0c0 .55-.45 1-1 
1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm3 0c0 .55-.45 1-1 
1s-1-.45-1-1v-6c0-.55.45-1 1-1s1 .45 1 1v6zm2-12h-4c0-.55-.45-1-1-1h-2c-.55 0-1 
.45-1 1h-4c-.55 0-1 .45-1 1v1h14v-1c0-.55-.45-1-1-1z"
+                      fill-rule="evenodd"
+                    />
+                  </svg>
+                </span>
+              </button>
+            </div>
             <div
               class="bp4-form-group right"
             >
@@ -374,7 +538,7 @@ exports[`RuleEditor matches snapshot with broadcast rule 
1`] = `
                   class="bp4-input"
                   placeholder="2010-01-01/2020-01-01"
                   type="text"
-                  value=""
+                  value="2010-01-01/2015-01-01"
                 />
               </div>
             </div>
@@ -400,7 +564,7 @@ exports[`RuleEditor matches snapshot with existing tier and 
non existing tier in
       <span
         class="bp4-button-text"
       >
-        loadByInterval(2010-01-01/2015-01-01)
+        loadByInterval(2010-01-01/2015-01-01, 3x)
       </span>
       <span
         aria-hidden="true"
@@ -550,7 +714,7 @@ exports[`RuleEditor matches snapshot with existing tier and 
non existing tier in
                   class="bp4-input"
                   placeholder="2010-01-01/2020-01-01"
                   type="text"
-                  value=""
+                  value="2010-01-01/2015-01-01"
                 />
               </div>
             </div>
@@ -586,7 +750,7 @@ exports[`RuleEditor matches snapshot with existing tier and 
non existing tier in
                     autocomplete="off"
                     class="bp4-input"
                     max="256"
-                    min="1"
+                    min="0"
                     type="text"
                     value="1"
                   />
@@ -620,9 +784,7 @@ exports[`RuleEditor matches snapshot with existing tier and 
non existing tier in
                   </button>
                   <button
                     aria-label="decrement"
-                    class="bp4-button bp4-disabled"
-                    disabled=""
-                    tabindex="-1"
+                    class="bp4-button"
                     type="button"
                   >
                     <span
@@ -745,7 +907,7 @@ exports[`RuleEditor matches snapshot with existing tier and 
non existing tier in
                     autocomplete="off"
                     class="bp4-input"
                     max="256"
-                    min="1"
+                    min="0"
                     type="text"
                     value="2"
                   />
@@ -936,7 +1098,7 @@ exports[`RuleEditor matches snapshot with existing tier in 
rule 1`] = `
       <span
         class="bp4-button-text"
       >
-        loadByInterval(2010-01-01/2015-01-01)
+        loadByInterval(2010-01-01/2015-01-01, 2x)
       </span>
       <span
         aria-hidden="true"
@@ -1086,7 +1248,7 @@ exports[`RuleEditor matches snapshot with existing tier 
in rule 1`] = `
                   class="bp4-input"
                   placeholder="2010-01-01/2020-01-01"
                   type="text"
-                  value=""
+                  value="2010-01-01/2015-01-01"
                 />
               </div>
             </div>
@@ -1122,7 +1284,7 @@ exports[`RuleEditor matches snapshot with existing tier 
in rule 1`] = `
                     autocomplete="off"
                     class="bp4-input"
                     max="256"
-                    min="1"
+                    min="0"
                     type="text"
                     value="2"
                   />
@@ -1315,7 +1477,7 @@ exports[`RuleEditor matches snapshot with non existing 
tier in rule 1`] = `
       <span
         class="bp4-button-text"
       >
-        loadByInterval(2010-01-01/2015-01-01)
+        loadByInterval(2010-01-01/2015-01-01, 2x)
       </span>
       <span
         aria-hidden="true"
@@ -1465,7 +1627,7 @@ exports[`RuleEditor matches snapshot with non existing 
tier in rule 1`] = `
                   class="bp4-input"
                   placeholder="2010-01-01/2020-01-01"
                   type="text"
-                  value=""
+                  value="2010-01-01/2015-01-01"
                 />
               </div>
             </div>
@@ -1501,7 +1663,7 @@ exports[`RuleEditor matches snapshot with non existing 
tier in rule 1`] = `
                     autocomplete="off"
                     class="bp4-input"
                     max="256"
-                    min="1"
+                    min="0"
                     type="text"
                     value="2"
                   />
diff --git a/web-console/src/components/rule-editor/rule-editor.spec.tsx 
b/web-console/src/components/rule-editor/rule-editor.spec.tsx
index 6d452b18c2..cfd3631ef6 100644
--- a/web-console/src/components/rule-editor/rule-editor.spec.tsx
+++ b/web-console/src/components/rule-editor/rule-editor.spec.tsx
@@ -25,7 +25,7 @@ describe('RuleEditor', () => {
   it('matches snapshot no tier in rule', () => {
     const ruleEditor = (
       <RuleEditor
-        rule={{ type: 'loadForever' }}
+        rule={{ type: 'loadForever', tieredReplicants: { test1: 1 } }}
         tiers={['test', 'test', 'test']}
         onChange={() => {}}
         onDelete={() => {}}
@@ -42,7 +42,7 @@ describe('RuleEditor', () => {
       <RuleEditor
         rule={{
           type: 'loadByInterval',
-          period: '2010-01-01/2015-01-01',
+          interval: '2010-01-01/2015-01-01',
           tieredReplicants: { nonexist: 2 },
         }}
         tiers={['test1', 'test2', 'test3']}
@@ -61,7 +61,7 @@ describe('RuleEditor', () => {
       <RuleEditor
         rule={{
           type: 'loadByInterval',
-          period: '2010-01-01/2015-01-01',
+          interval: '2010-01-01/2015-01-01',
           tieredReplicants: { test1: 2 },
         }}
         tiers={['test1', 'test2', 'test3']}
@@ -80,7 +80,7 @@ describe('RuleEditor', () => {
       <RuleEditor
         rule={{
           type: 'loadByInterval',
-          period: '2010-01-01/2015-01-01',
+          interval: '2010-01-01/2015-01-01',
           tieredReplicants: {
             test1: 2,
             nonexist: 1,
@@ -102,7 +102,7 @@ describe('RuleEditor', () => {
       <RuleEditor
         rule={{
           type: 'broadcastByInterval',
-          period: '2010-01-01/2015-01-01',
+          interval: '2010-01-01/2015-01-01',
         }}
         tiers={[]}
         onChange={() => {}}
diff --git a/web-console/src/components/rule-editor/rule-editor.tsx 
b/web-console/src/components/rule-editor/rule-editor.tsx
index c2546d62c6..2556c891f5 100644
--- a/web-console/src/components/rule-editor/rule-editor.tsx
+++ b/web-console/src/components/rule-editor/rule-editor.tsx
@@ -91,7 +91,7 @@ export const RuleEditor = React.memo(function 
RuleEditor(props: RuleEditorProps)
               if (isNaN(v)) return;
               onChange(RuleUtil.addTieredReplicant(rule, tier, v));
             }}
-            min={1}
+            min={0}
             max={256}
           />
           <Button minimal style={{ pointerEvents: 'none' }}>
diff --git 
a/web-console/src/components/segment-timeline/segment-timeline.spec.tsx 
b/web-console/src/components/segment-timeline/segment-timeline.spec.tsx
index 1d7fb6aab5..7dca6bb4e1 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.spec.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.spec.tsx
@@ -20,7 +20,7 @@ import { render } from '@testing-library/react';
 import { sane } from 'druid-query-toolkit';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 
 import { SegmentTimeline } from './segment-timeline';
 
diff --git a/web-console/src/components/segment-timeline/segment-timeline.tsx 
b/web-console/src/components/segment-timeline/segment-timeline.tsx
index f8cef06189..006b84e660 100644
--- a/web-console/src/components/segment-timeline/segment-timeline.tsx
+++ b/web-console/src/components/segment-timeline/segment-timeline.tsx
@@ -28,9 +28,9 @@ import { AxisScale } from 'd3-axis';
 import { scaleLinear, scaleUtc } from 'd3-scale';
 import React from 'react';
 
+import { Capabilities } from '../../helpers';
 import { Api } from '../../singletons';
 import {
-  Capabilities,
   ceilToUtcDay,
   formatBytes,
   formatInteger,
diff --git a/web-console/src/console-application.tsx 
b/web-console/src/console-application.tsx
index 02845835dd..24947636d6 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -25,8 +25,9 @@ import { HashRouter, Route, Switch } from 'react-router-dom';
 
 import { HeaderActiveTab, HeaderBar, Loader } from './components';
 import { DruidEngine, QueryWithContext } from './druid-models';
+import { Capabilities } from './helpers';
 import { AppToaster } from './singletons';
-import { Capabilities, QueryManager } from './utils';
+import { localStorageGetJson, LocalStorageKeys, QueryManager } from './utils';
 import {
   DatasourcesView,
   HomeView,
@@ -89,9 +90,17 @@ export class ConsoleApplication extends React.PureComponent<
 
     this.capabilitiesQueryManager = new QueryManager({
       processQuery: async () => {
-        const capabilities = await Capabilities.detectCapabilities();
-        if (!capabilities) ConsoleApplication.shownServiceNotification();
-        return capabilities || Capabilities.FULL;
+        const capabilitiesOverride = 
localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
+        const capabilities = capabilitiesOverride
+          ? new Capabilities(capabilitiesOverride)
+          : await Capabilities.detectCapabilities();
+
+        if (!capabilities) {
+          ConsoleApplication.shownServiceNotification();
+          return Capabilities.FULL;
+        }
+
+        return await Capabilities.detectCapacity(capabilities);
       },
       onStateChange: ({ data, loading }) => {
         this.setState({
@@ -259,6 +268,7 @@ export class ConsoleApplication extends React.PureComponent<
     return this.wrapInViewContainer(
       'workbench',
       <WorkbenchView
+        capabilities={capabilities}
         tabId={p.match.params.tabId}
         onTabChange={newTabId => {
           location.hash = `#workbench/${newTabId}`;
@@ -275,9 +285,14 @@ export class ConsoleApplication extends 
React.PureComponent<
   };
 
   private readonly wrappedSqlDataLoaderView = () => {
+    const { capabilities } = this.state;
     return this.wrapInViewContainer(
       'sql-data-loader',
-      <SqlDataLoaderView goToQuery={this.goToQuery} 
goToIngestion={this.goToIngestionWithTaskId} />,
+      <SqlDataLoaderView
+        capabilities={capabilities}
+        goToQuery={this.goToQuery}
+        goToIngestion={this.goToIngestionWithTaskId}
+      />,
     );
   };
 
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 de558d3998..7256ded281 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
@@ -90,7 +90,7 @@ exports[`RetentionDialog matches snapshot 1`] = `
                     <span
                       class="bp4-button-text"
                     >
-                      loadByPeriod(P1000Y+future)
+                      loadByPeriod(P1000Y+future, 2x)
                     </span>
                     <span
                       aria-hidden="true"
@@ -325,7 +325,7 @@ exports[`RetentionDialog matches snapshot 1`] = `
                                   autocomplete="off"
                                   class="bp4-input"
                                   max="256"
-                                  min="1"
+                                  min="0"
                                   type="text"
                                   value="2"
                                 />
@@ -562,7 +562,7 @@ exports[`RetentionDialog matches snapshot 1`] = `
                   <span
                     class="bp4-button-text"
                   >
-                    loadForever
+                    loadForever(2x)
                   </span>
                 </button>
               </div>
diff --git a/web-console/src/druid-models/execution/execution.ts 
b/web-console/src/druid-models/execution/execution.ts
index f2ae3f21fa..32c8e62056 100644
--- a/web-console/src/druid-models/execution/execution.ts
+++ b/web-console/src/druid-models/execution/execution.ts
@@ -106,6 +106,7 @@ function getUsageInfoFromStatusPayload(status: any): 
UsageInfo | undefined {
 }
 
 export interface CapacityInfo {
+  availableTaskSlots: number;
   usedTaskSlots: number;
   totalTaskSlots: number;
 }
@@ -125,8 +126,7 @@ function formatPendingMessage(
     return baseMessage;
   }
 
-  const { usedTaskSlots, totalTaskSlots } = capacityInfo;
-  const availableTaskSlots = totalTaskSlots - usedTaskSlots;
+  const { availableTaskSlots, usedTaskSlots, totalTaskSlots } = capacityInfo;
 
   // If there are enough slots free: "Launched 2/4 tasks." (It will resolve 
very soon, no need to make it complicated.)
   if (pendingTasks <= availableTaskSlots) {
diff --git a/web-console/src/druid-models/query-context/query-context.tsx 
b/web-console/src/druid-models/query-context/query-context.tsx
index f915186285..302f88049d 100644
--- a/web-console/src/druid-models/query-context/query-context.tsx
+++ b/web-console/src/druid-models/query-context/query-context.tsx
@@ -116,9 +116,8 @@ export function changeTimezone(context: QueryContext, 
timezone: string | undefin
 
 // maxNumTasks
 
-export function getMaxNumTasks(context: QueryContext): number {
-  const { maxNumTasks } = context;
-  return Math.max(typeof maxNumTasks === 'number' ? maxNumTasks : 0, 2);
+export function getMaxNumTasks(context: QueryContext): number | undefined {
+  return context.maxNumTasks;
 }
 
 export function changeMaxNumTasks(
diff --git a/web-console/src/druid-models/workbench-query/workbench-query.ts 
b/web-console/src/druid-models/workbench-query/workbench-query.ts
index 260b131b72..32ef417c59 100644
--- a/web-console/src/druid-models/workbench-query/workbench-query.ts
+++ b/web-console/src/druid-models/workbench-query/workbench-query.ts
@@ -485,6 +485,13 @@ export class WorkbenchQuery {
     return ret.changeQueryString(newQueryString);
   }
 
+  public setMaxNumTasksIfUnset(maxNumTasks: number | undefined): 
WorkbenchQuery {
+    const { queryContext } = this;
+    if (typeof queryContext.maxNumTasks === 'number' || !maxNumTasks) return 
this;
+
+    return this.changeQueryContext({ ...queryContext, maxNumTasks: 
Math.max(maxNumTasks, 2) });
+  }
+
   public getApiQuery(makeQueryId: () => string = uuidv4): {
     engine: DruidEngine;
     query: Record<string, any>;
diff --git a/web-console/src/utils/capabilities.ts 
b/web-console/src/helpers/capabilities.ts
similarity index 86%
rename from web-console/src/utils/capabilities.ts
rename to web-console/src/helpers/capabilities.ts
index e5bb338254..87ff3a53b2 100644
--- a/web-console/src/utils/capabilities.ts
+++ b/web-console/src/helpers/capabilities.ts
@@ -18,7 +18,7 @@
 
 import { Api } from '../singletons';
 
-import { localStorageGetJson, LocalStorageKeys } from './local-storage-keys';
+import { maybeGetClusterCapacity } from './index';
 
 export type CapabilitiesMode = 'full' | 'no-sql' | 'no-proxy';
 
@@ -33,13 +33,12 @@ export type CapabilitiesModeExtended =
 
 export type QueryType = 'none' | 'nativeOnly' | 'nativeAndSql';
 
-export interface CapabilitiesOptions {
+export interface CapabilitiesValue {
   queryType: QueryType;
   multiStageQuery: boolean;
   coordinator: boolean;
   overlord: boolean;
-
-  warnings?: string[];
+  clusterCapacity?: number;
 }
 
 export class Capabilities {
@@ -55,6 +54,7 @@ export class Capabilities {
   private readonly multiStageQuery: boolean;
   private readonly coordinator: boolean;
   private readonly overlord: boolean;
+  private readonly clusterCapacity?: number;
 
   static async detectQueryType(): Promise<QueryType | undefined> {
     // Check SQL endpoint
@@ -137,9 +137,6 @@ export class Capabilities {
   }
 
   static async detectCapabilities(): Promise<Capabilities | undefined> {
-    const capabilitiesOverride = 
localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
-    if (capabilitiesOverride) return new Capabilities(capabilitiesOverride);
-
     const queryType = await Capabilities.detectQueryType();
     if (typeof queryType === 'undefined') return;
 
@@ -164,11 +161,34 @@ export class Capabilities {
     });
   }
 
-  constructor(options: CapabilitiesOptions) {
-    this.queryType = options.queryType;
-    this.multiStageQuery = options.multiStageQuery;
-    this.coordinator = options.coordinator;
-    this.overlord = options.overlord;
+  static async detectCapacity(capabilities: Capabilities): 
Promise<Capabilities> {
+    if (!capabilities.hasOverlordAccess()) return capabilities;
+
+    const capacity = await maybeGetClusterCapacity();
+    if (!capacity) return capabilities;
+
+    return new Capabilities({
+      ...capabilities.valueOf(),
+      clusterCapacity: capacity.totalTaskSlots,
+    });
+  }
+
+  constructor(value: CapabilitiesValue) {
+    this.queryType = value.queryType;
+    this.multiStageQuery = value.multiStageQuery;
+    this.coordinator = value.coordinator;
+    this.overlord = value.overlord;
+    this.clusterCapacity = value.clusterCapacity;
+  }
+
+  public valueOf(): CapabilitiesValue {
+    return {
+      queryType: this.queryType,
+      multiStageQuery: this.multiStageQuery,
+      coordinator: this.coordinator,
+      overlord: this.overlord,
+      clusterCapacity: this.clusterCapacity,
+    };
   }
 
   public getMode(): CapabilitiesMode {
@@ -240,6 +260,10 @@ export class Capabilities {
   public hasSqlOrOverlordAccess(): boolean {
     return this.hasSql() || this.hasOverlordAccess();
   }
+
+  public getClusterCapacity(): number | undefined {
+    return this.clusterCapacity;
+  }
 }
 Capabilities.FULL = new Capabilities({
   queryType: 'nativeAndSql',
diff --git a/web-console/src/helpers/capacity.ts 
b/web-console/src/helpers/capacity.ts
index 1ded088952..8a6a0fffa4 100644
--- a/web-console/src/helpers/capacity.ts
+++ b/web-console/src/helpers/capacity.ts
@@ -35,7 +35,11 @@ export async function getClusterCapacity(): 
Promise<CapacityInfo> {
     Number(workerInfo.worker.capacity),
   );
 
-  return { usedTaskSlots, totalTaskSlots };
+  return {
+    availableTaskSlots: totalTaskSlots - usedTaskSlots,
+    usedTaskSlots,
+    totalTaskSlots,
+  };
 }
 
 export async function maybeGetClusterCapacity(): Promise<CapacityInfo | 
undefined> {
diff --git a/web-console/src/helpers/index.ts b/web-console/src/helpers/index.ts
index be2b4cef77..fc238ef4e3 100644
--- a/web-console/src/helpers/index.ts
+++ b/web-console/src/helpers/index.ts
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+export * from './capabilities';
 export * from './capacity';
 export * from './execution/general';
 export * from './execution/sql-task-execution';
diff --git a/web-console/src/hooks/index.ts b/web-console/src/hooks/index.ts
index 8b47db5519..9ffb376255 100644
--- a/web-console/src/hooks/index.ts
+++ b/web-console/src/hooks/index.ts
@@ -16,6 +16,7 @@
  * limitations under the License.
  */
 
+export * from './use-clock';
 export * from './use-constant';
 export * from './use-global-event-listener';
 export * from './use-interval';
diff --git 
a/web-console/src/views/home-view/segments-card/segments-card.spec.tsx 
b/web-console/src/hooks/use-clock.ts
similarity index 65%
copy from web-console/src/views/home-view/segments-card/segments-card.spec.tsx
copy to web-console/src/hooks/use-clock.ts
index 2b7b6c9dc6..5d12644219 100644
--- a/web-console/src/views/home-view/segments-card/segments-card.spec.tsx
+++ b/web-console/src/hooks/use-clock.ts
@@ -16,18 +16,26 @@
  * limitations under the License.
  */
 
-import { render } from '@testing-library/react';
-import React from 'react';
+import { useEffect, useState } from 'react';
 
-import { Capabilities } from '../../../utils';
+function getNowToSecond(): Date {
+  const now = new Date();
+  now.setMilliseconds(0);
+  return now;
+}
 
-import { SegmentsCard } from './segments-card';
+export function useClock() {
+  const [now, setNow] = useState<Date>(getNowToSecond);
 
-describe('SegmentsCard', () => {
-  it('matches snapshot', () => {
-    const segmentsCard = <SegmentsCard capabilities={Capabilities.FULL} />;
+  useEffect(() => {
+    const checkInterval = setInterval(() => {
+      setNow(getNowToSecond());
+    }, 1000);
 
-    const { container } = render(segmentsCard);
-    expect(container.firstChild).toMatchSnapshot();
-  });
-});
+    return () => {
+      clearInterval(checkInterval);
+    };
+  }, []);
+
+  return now;
+}
diff --git a/web-console/src/utils/index.tsx b/web-console/src/utils/index.tsx
index 967a1144d2..c27a070b97 100644
--- a/web-console/src/utils/index.tsx
+++ b/web-console/src/utils/index.tsx
@@ -16,7 +16,6 @@
  * limitations under the License.
  */
 
-export * from './capabilities';
 export * from './column-metadata';
 export * from './date';
 export * from './download';
diff --git a/web-console/src/utils/load-rule.ts 
b/web-console/src/utils/load-rule.ts
index 6cae0a306c..843d4b8bc8 100644
--- a/web-console/src/utils/load-rule.ts
+++ b/web-console/src/utils/load-rule.ts
@@ -53,11 +53,14 @@ export class RuleUtil {
   ];
 
   static ruleToString(rule: Rule): string {
-    return [
-      rule.type,
-      rule.period ? `(${rule.period}${rule.includeFuture ? `+future` : ''})` : 
'',
-      rule.interval ? `(${rule.interval})` : '',
-    ].join('');
+    const params: string[] = [];
+
+    if (RuleUtil.hasPeriod(rule))
+      params.push(`${rule.period}${rule.includeFuture ? '+future' : ''}`);
+    if (RuleUtil.hasInterval(rule)) params.push(rule.interval || '?');
+    if (RuleUtil.hasTieredReplicants(rule)) 
params.push(`${RuleUtil.totalReplicas(rule)}x`);
+
+    return `${rule.type}(${params.join(', ')})`;
   }
 
   static changeRuleType(rule: Rule, type: RuleType): Rule {
@@ -121,4 +124,23 @@ export class RuleUtil {
     const newTieredReplicants = deepSet(rule.tieredReplicants || {}, tier, 
replication);
     return deepSet(rule, 'tieredReplicants', newTieredReplicants);
   }
+
+  static totalReplicas(rule: Rule): number {
+    const tieredReplicants = rule.tieredReplicants || {};
+    let total = 0;
+    for (const k in tieredReplicants) {
+      total += tieredReplicants[k];
+    }
+    return total;
+  }
+
+  static isColdRule(rule: Rule): boolean {
+    return RuleUtil.hasTieredReplicants(rule) && RuleUtil.totalReplicas(rule) 
=== 0;
+  }
+
+  static hasColdRule(rules: Rule[] | undefined, defaultRules: Rule[] | 
undefined): boolean {
+    return (
+      (rules || []).some(RuleUtil.isColdRule) || (defaultRules || 
[]).some(RuleUtil.isColdRule)
+    );
+  }
 }
diff --git a/web-console/src/views/datasources-view/datasources-view.spec.tsx 
b/web-console/src/views/datasources-view/datasources-view.spec.tsx
index e4231bceb3..9eff979730 100644
--- a/web-console/src/views/datasources-view/datasources-view.spec.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.spec.tsx
@@ -19,7 +19,7 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 
 import { DatasourcesView } from './datasources-view';
 
diff --git a/web-console/src/views/datasources-view/datasources-view.tsx 
b/web-console/src/views/datasources-view/datasources-view.tsx
index 38b85388d2..ce3332d389 100644
--- a/web-console/src/views/datasources-view/datasources-view.tsx
+++ b/web-console/src/views/datasources-view/datasources-view.tsx
@@ -51,11 +51,10 @@ import {
   QueryWithContext,
   zeroCompactionStatus,
 } from '../../druid-models';
+import { Capabilities, CapabilitiesMode } from '../../helpers';
 import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from 
'../../react-table';
 import { Api, AppToaster } from '../../singletons';
 import {
-  Capabilities,
-  CapabilitiesMode,
   compact,
   countBy,
   deepGet,
@@ -289,37 +288,37 @@ export class DatasourcesView extends React.PureComponent<
       [
         visibleColumns.shown('Datasource name') && `datasource`,
         (visibleColumns.shown('Availability') || visibleColumns.shown('Segment 
granularity')) &&
-          `COUNT(*) FILTER (WHERE (is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AS num_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1) AS num_segments`,
         (visibleColumns.shown('Availability') || 
visibleColumns.shown('Availability detail')) && [
           `COUNT(*) FILTER (WHERE is_published = 1 AND is_overshadowed = 0 AND 
is_available = 0) AS num_segments_to_load`,
-          `COUNT(*) FILTER (WHERE is_available = 1 AND NOT ((is_published = 1 
AND is_overshadowed = 0) OR is_realtime = 1)) AS num_segments_to_drop`,
+          `COUNT(*) FILTER (WHERE is_available = 1 AND is_active = 0) AS 
num_segments_to_drop`,
         ],
         visibleColumns.shown('Total data size') &&
-          `SUM("size") FILTER (WHERE is_published = 1 AND is_overshadowed = 0) 
AS total_data_size`,
+          `SUM("size") FILTER (WHERE is_active = 1) AS total_data_size`,
         visibleColumns.shown('Segment rows') && [
-          `MIN("num_rows") FILTER (WHERE is_published = 1 AND is_overshadowed 
= 0) AS min_segment_rows`,
-          `AVG("num_rows") FILTER (WHERE is_published = 1 AND is_overshadowed 
= 0) AS avg_segment_rows`,
-          `MAX("num_rows") FILTER (WHERE is_published = 1 AND is_overshadowed 
= 0) AS max_segment_rows`,
+          `MIN("num_rows") FILTER (WHERE is_available = 1 AND is_realtime = 0) 
AS min_segment_rows`,
+          `AVG("num_rows") FILTER (WHERE is_available = 1 AND is_realtime = 0) 
AS avg_segment_rows`,
+          `MAX("num_rows") FILTER (WHERE is_available = 1 AND is_realtime = 0) 
AS max_segment_rows`,
         ],
         visibleColumns.shown('Segment size') && [
-          `MIN("size") FILTER (WHERE is_published = 1 AND is_overshadowed = 0) 
AS min_segment_size`,
-          `AVG("size") FILTER (WHERE is_published = 1 AND is_overshadowed = 0) 
AS avg_segment_size`,
-          `MAX("size") FILTER (WHERE is_published = 1 AND is_overshadowed = 0) 
AS max_segment_size`,
+          `MIN("size") FILTER (WHERE is_active = 1 AND is_realtime = 0) AS 
min_segment_size`,
+          `AVG("size") FILTER (WHERE is_active = 1 AND is_realtime = 0) AS 
avg_segment_size`,
+          `MAX("size") FILTER (WHERE is_active = 1 AND is_realtime = 0) AS 
max_segment_size`,
         ],
         visibleColumns.shown('Segment granularity') && [
-          `COUNT(*) FILTER (WHERE ((is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AND "start" LIKE '%:00.000Z' AND "end" LIKE '%:00.000Z') AS 
minute_aligned_segments`,
-          `COUNT(*) FILTER (WHERE ((is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AND "start" LIKE '%:00:00.000Z' AND "end" LIKE 
'%:00:00.000Z') AS hour_aligned_segments`,
-          `COUNT(*) FILTER (WHERE ((is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AND "start" LIKE '%T00:00:00.000Z' AND "end" LIKE 
'%T00:00:00.000Z') AS day_aligned_segments`,
-          `COUNT(*) FILTER (WHERE ((is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AND "start" LIKE '%-01T00:00:00.000Z' AND "end" LIKE 
'%-01T00:00:00.000Z') AS month_aligned_segments`,
-          `COUNT(*) FILTER (WHERE ((is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AND "start" LIKE '%-01-01T00:00:00.000Z' AND "end" LIKE 
'%-01-01T00:00:00.000Z') AS year_aligned_segments`,
-          `COUNT(*) FILTER (WHERE ((is_published = 1 AND is_overshadowed = 0) 
OR is_realtime = 1) AND "start" = '-146136543-09-08T08:23:32.096Z' AND "end" = 
'146140482-04-24T15:36:27.903Z') AS all_granularity_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1 AND "start" LIKE '%:00.000Z' 
AND "end" LIKE '%:00.000Z') AS minute_aligned_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1 AND "start" LIKE 
'%:00:00.000Z' AND "end" LIKE '%:00:00.000Z') AS hour_aligned_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1 AND "start" LIKE 
'%T00:00:00.000Z' AND "end" LIKE '%T00:00:00.000Z') AS day_aligned_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1 AND "start" LIKE 
'%-01T00:00:00.000Z' AND "end" LIKE '%-01T00:00:00.000Z') AS 
month_aligned_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1 AND "start" LIKE 
'%-01-01T00:00:00.000Z' AND "end" LIKE '%-01-01T00:00:00.000Z') AS 
year_aligned_segments`,
+          `COUNT(*) FILTER (WHERE is_active = 1 AND "start" = 
'-146136543-09-08T08:23:32.096Z' AND "end" = '146140482-04-24T15:36:27.903Z') 
AS all_granularity_segments`,
         ],
         visibleColumns.shown('Total rows') &&
-          `SUM("num_rows") FILTER (WHERE (is_published = 1 AND is_overshadowed 
= 0) OR is_realtime = 1) AS total_rows`,
+          `SUM("num_rows") FILTER (WHERE is_active = 1) AS total_rows`,
         visibleColumns.shown('Avg. row size') &&
-          `CASE WHEN SUM("num_rows") FILTER (WHERE is_published = 1 AND 
is_overshadowed = 0) <> 0 THEN (SUM("size") FILTER (WHERE is_published = 1 AND 
is_overshadowed = 0) / SUM("num_rows") FILTER (WHERE is_published = 1 AND 
is_overshadowed = 0)) ELSE 0 END AS avg_row_size`,
+          `CASE WHEN SUM("num_rows") FILTER (WHERE is_available = 1) <> 0 THEN 
(SUM("size") FILTER (WHERE is_available = 1) / SUM("num_rows") FILTER (WHERE 
is_available = 1)) ELSE 0 END AS avg_row_size`,
         visibleColumns.shown('Replicated size') &&
-          `SUM("size" * "num_replicas") FILTER (WHERE is_published = 1 AND 
is_overshadowed = 0) AS replicated_size`,
+          `SUM("size" * "num_replicas") FILTER (WHERE is_active = 1) AS 
replicated_size`,
       ].flat(),
     );
 
@@ -1080,7 +1079,7 @@ ORDER BY 1`;
             accessor: 'num_segments',
             className: 'padded',
             Cell: ({ value: num_segments, original }) => {
-              const { datasource, unused, num_segments_to_load } = original as 
Datasource;
+              const { datasource, unused, num_segments_to_load, rules } = 
original as Datasource;
               if (unused) {
                 return (
                   <span>
@@ -1090,6 +1089,7 @@ ORDER BY 1`;
                 );
               }
 
+              const hasCold = RuleUtil.hasColdRule(rules, defaultRules);
               const segmentsEl = (
                 <a onClick={() => goToSegments(datasource)}>
                   {pluralIfNeeded(num_segments, 'segment')}
@@ -1104,13 +1104,17 @@ ORDER BY 1`;
                     Empty
                   </span>
                 );
-              } else if (num_segments_to_load === 0) {
+              } else if (num_segments_to_load === 0 || hasCold) {
+                const numAvailableSegments = num_segments - 
num_segments_to_load;
+                const percentHot = (
+                  Math.floor((numAvailableSegments / num_segments) * 1000) / 10
+                ).toFixed(1);
                 return (
                   <span>
                     <span style={{ color: 
DatasourcesView.FULLY_AVAILABLE_COLOR }}>
                       &#x25cf;&nbsp;
                     </span>
-                    Fully available ({segmentsEl})
+                    Fully available{hasCold ? `, ${percentHot}% hot` : ''} 
({segmentsEl})
                   </span>
                 );
               } else {
@@ -1142,7 +1146,10 @@ ORDER BY 1`;
             width: 180,
             className: 'padded',
             Cell: ({ original }) => {
-              const { num_segments_to_load, num_segments_to_drop } = original 
as Datasource;
+              const { num_segments_to_load, num_segments_to_drop, rules } = 
original as Datasource;
+              if (RuleUtil.hasColdRule(rules, defaultRules)) {
+                return pluralIfNeeded(num_segments_to_load, 'cold segment');
+              }
               return formatLoadDrop(num_segments_to_load, 
num_segments_to_drop);
             },
           },
diff --git 
a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap 
b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
index 78c458fbb4..c69a4777f9 100644
--- a/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
+++ b/web-console/src/views/home-view/__snapshots__/home-view.spec.tsx.snap
@@ -8,6 +8,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
   <Memo(DatasourcesCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": false,
         "overlord": false,
@@ -18,6 +19,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
   <Memo(SegmentsCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": false,
         "overlord": false,
@@ -28,6 +30,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
   <Memo(ServicesCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": false,
         "overlord": false,
@@ -38,6 +41,7 @@ exports[`HomeView matches snapshot (coordinator) 1`] = `
   <Memo(LookupsCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": false,
         "overlord": false,
@@ -56,6 +60,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
   <Memo(DatasourcesCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": true,
         "overlord": true,
@@ -66,6 +71,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
   <Memo(SegmentsCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": true,
         "overlord": true,
@@ -76,6 +82,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
   <Memo(SupervisorsCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": true,
         "overlord": true,
@@ -86,6 +93,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
   <Memo(TasksCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": true,
         "overlord": true,
@@ -96,6 +104,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
   <Memo(ServicesCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": true,
         "overlord": true,
@@ -106,6 +115,7 @@ exports[`HomeView matches snapshot (full) 1`] = `
   <Memo(LookupsCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": true,
         "multiStageQuery": true,
         "overlord": true,
@@ -124,6 +134,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = `
   <Memo(SupervisorsCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": false,
         "multiStageQuery": false,
         "overlord": true,
@@ -134,6 +145,7 @@ exports[`HomeView matches snapshot (overlord) 1`] = `
   <Memo(TasksCard)
     capabilities={
       Capabilities {
+        "clusterCapacity": undefined,
         "coordinator": false,
         "multiStageQuery": false,
         "overlord": true,
diff --git 
a/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx 
b/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx
index 9874f05f00..358d5d144a 100644
--- a/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx
+++ b/web-console/src/views/home-view/datasources-card/datasources-card.spec.tsx
@@ -19,7 +19,7 @@
 import { render } from '@testing-library/react';
 import React from 'react';
 
-import { Capabilities } from '../../../utils';
+import { Capabilities } from '../../../helpers';
 
 import { DatasourcesCard } from './datasources-card';
 
diff --git 
a/web-console/src/views/home-view/datasources-card/datasources-card.tsx 
b/web-console/src/views/home-view/datasources-card/datasources-card.tsx
index a3f87bdd57..15f5616e36 100644
--- a/web-console/src/views/home-view/datasources-card/datasources-card.tsx
+++ b/web-console/src/views/home-view/datasources-card/datasources-card.tsx
@@ -19,9 +19,10 @@
 import { IconNames } from '@blueprintjs/icons';
 import React from 'react';
 
+import { Capabilities } from '../../../helpers';
 import { useQueryManager } from '../../../hooks';
 import { Api } from '../../../singletons';
-import { Capabilities, pluralIfNeeded, queryDruidSql } from '../../../utils';
+import { pluralIfNeeded, queryDruidSql } from '../../../utils';
 import { HomeViewCard } from '../home-view-card/home-view-card';
 
 export interface DatasourcesCardProps {
@@ -31,7 +32,7 @@ export interface DatasourcesCardProps {
 export const DatasourcesCard = React.memo(function DatasourcesCard(props: 
DatasourcesCardProps) {
   const [datasourceCountState] = useQueryManager<Capabilities, number>({
     processQuery: async capabilities => {
-      let datasources: string[];
+      let datasources: any[];
       if (capabilities.hasSql()) {
         datasources = await queryDruidSql({
           query: `SELECT datasource FROM sys.segments GROUP BY 1`,
diff --git a/web-console/src/views/home-view/home-view-card/home-view-card.scss 
b/web-console/src/views/home-view/home-view-card/home-view-card.scss
index 171f99b1cf..c5c0596842 100644
--- a/web-console/src/views/home-view/home-view-card/home-view-card.scss
+++ b/web-console/src/views/home-view/home-view-card/home-view-card.scss
@@ -21,7 +21,7 @@
 
 .home-view-card {
   .#{$bp-ns}-card {
-    height: 170px;
+    height: 200px;
   }
 
   &:hover {
diff --git a/web-console/src/views/home-view/home-view.spec.tsx 
b/web-console/src/views/home-view/home-view.spec.tsx
index 49847bea41..81f46cc296 100644
--- a/web-console/src/views/home-view/home-view.spec.tsx
+++ b/web-console/src/views/home-view/home-view.spec.tsx
@@ -19,7 +19,7 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 
 import { HomeView } from './home-view';
 
diff --git a/web-console/src/views/home-view/home-view.tsx 
b/web-console/src/views/home-view/home-view.tsx
index 29295236da..08ef69b2f4 100644
--- a/web-console/src/views/home-view/home-view.tsx
+++ b/web-console/src/views/home-view/home-view.tsx
@@ -18,7 +18,7 @@
 
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 
 import { DatasourcesCard } from './datasources-card/datasources-card';
 import { LookupsCard } from './lookups-card/lookups-card';
diff --git a/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx 
b/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx
index 8294b6f0a1..8abd8d422a 100644
--- a/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx
+++ b/web-console/src/views/home-view/lookups-card/lookups-card.spec.tsx
@@ -19,7 +19,7 @@
 import { render } from '@testing-library/react';
 import React from 'react';
 
-import { Capabilities } from '../../../utils';
+import { Capabilities } from '../../../helpers';
 
 import { LookupsCard } from './lookups-card';
 
diff --git a/web-console/src/views/home-view/lookups-card/lookups-card.tsx 
b/web-console/src/views/home-view/lookups-card/lookups-card.tsx
index e6fc612458..e4f5f98b28 100644
--- a/web-console/src/views/home-view/lookups-card/lookups-card.tsx
+++ b/web-console/src/views/home-view/lookups-card/lookups-card.tsx
@@ -20,9 +20,10 @@ import { IconNames } from '@blueprintjs/icons';
 import { sum } from 'd3-array';
 import React from 'react';
 
+import { Capabilities } from '../../../helpers';
 import { useQueryManager } from '../../../hooks';
 import { Api } from '../../../singletons';
-import { Capabilities, isLookupsUninitialized, pluralIfNeeded } from 
'../../../utils';
+import { isLookupsUninitialized, pluralIfNeeded } from '../../../utils';
 import { HomeViewCard } from '../home-view-card/home-view-card';
 
 export interface LookupsCardProps {
diff --git 
a/web-console/src/views/home-view/segments-card/segments-card.spec.tsx 
b/web-console/src/views/home-view/segments-card/segments-card.spec.tsx
index 2b7b6c9dc6..59ff146483 100644
--- a/web-console/src/views/home-view/segments-card/segments-card.spec.tsx
+++ b/web-console/src/views/home-view/segments-card/segments-card.spec.tsx
@@ -19,7 +19,7 @@
 import { render } from '@testing-library/react';
 import React from 'react';
 
-import { Capabilities } from '../../../utils';
+import { Capabilities } from '../../../helpers';
 
 import { SegmentsCard } from './segments-card';
 
diff --git a/web-console/src/views/home-view/segments-card/segments-card.tsx 
b/web-console/src/views/home-view/segments-card/segments-card.tsx
index 3ffcedf3de..56c022fc01 100644
--- a/web-console/src/views/home-view/segments-card/segments-card.tsx
+++ b/web-console/src/views/home-view/segments-card/segments-card.tsx
@@ -20,9 +20,10 @@ import { IconNames } from '@blueprintjs/icons';
 import { sum } from 'd3-array';
 import React from 'react';
 
+import { Capabilities } from '../../../helpers';
 import { useQueryManager } from '../../../hooks';
 import { Api } from '../../../singletons';
-import { Capabilities, deepGet, pluralIfNeeded, queryDruidSql } from 
'../../../utils';
+import { deepGet, pluralIfNeeded, queryDruidSql } from '../../../utils';
 import { HomeViewCard } from '../home-view-card/home-view-card';
 
 export interface SegmentCounts {
diff --git 
a/web-console/src/views/home-view/services-card/services-card.spec.tsx 
b/web-console/src/views/home-view/services-card/services-card.spec.tsx
index 8acfe1a1e6..5cc7fa1d88 100644
--- a/web-console/src/views/home-view/services-card/services-card.spec.tsx
+++ b/web-console/src/views/home-view/services-card/services-card.spec.tsx
@@ -19,7 +19,7 @@
 import { render } from '@testing-library/react';
 import React from 'react';
 
-import { Capabilities } from '../../../utils';
+import { Capabilities } from '../../../helpers';
 
 import { ServicesCard } from './services-card';
 
diff --git a/web-console/src/views/home-view/services-card/services-card.tsx 
b/web-console/src/views/home-view/services-card/services-card.tsx
index 846aebfc1e..88594bb69c 100644
--- a/web-console/src/views/home-view/services-card/services-card.tsx
+++ b/web-console/src/views/home-view/services-card/services-card.tsx
@@ -20,9 +20,10 @@ import { IconNames } from '@blueprintjs/icons';
 import React from 'react';
 
 import { PluralPairIfNeeded } from '../../../components';
+import { Capabilities } from '../../../helpers';
 import { useQueryManager } from '../../../hooks';
 import { Api } from '../../../singletons';
-import { Capabilities, lookupBy, queryDruidSql } from '../../../utils';
+import { lookupBy, queryDruidSql } from '../../../utils';
 import { HomeViewCard } from '../home-view-card/home-view-card';
 
 export interface ServiceCounts {
diff --git 
a/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx 
b/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx
index 8624524841..ad406de096 100644
--- a/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx
+++ b/web-console/src/views/home-view/supervisors-card/supervisors-card.spec.tsx
@@ -19,7 +19,7 @@
 import { render } from '@testing-library/react';
 import React from 'react';
 
-import { Capabilities } from '../../../utils';
+import { Capabilities } from '../../../helpers';
 
 import { SupervisorsCard } from './supervisors-card';
 
diff --git 
a/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx 
b/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx
index 09566d7353..a04d7b5929 100644
--- a/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx
+++ b/web-console/src/views/home-view/supervisors-card/supervisors-card.tsx
@@ -19,9 +19,10 @@
 import { IconNames } from '@blueprintjs/icons';
 import React from 'react';
 
+import { Capabilities } from '../../../helpers';
 import { useQueryManager } from '../../../hooks';
 import { Api } from '../../../singletons';
-import { Capabilities, pluralIfNeeded, queryDruidSql } from '../../../utils';
+import { pluralIfNeeded, queryDruidSql } from '../../../utils';
 import { HomeViewCard } from '../home-view-card/home-view-card';
 
 export interface SupervisorCounts {
diff --git a/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx 
b/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx
index 8f98dfd2ea..111384e7af 100644
--- a/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx
+++ b/web-console/src/views/home-view/tasks-card/tasks-card.spec.tsx
@@ -19,7 +19,7 @@
 import { render } from '@testing-library/react';
 import React from 'react';
 
-import { Capabilities } from '../../../utils';
+import { Capabilities } from '../../../helpers';
 
 import { TasksCard } from './tasks-card';
 
diff --git a/web-console/src/views/home-view/tasks-card/tasks-card.tsx 
b/web-console/src/views/home-view/tasks-card/tasks-card.tsx
index 7e4f678829..27b29320b7 100644
--- a/web-console/src/views/home-view/tasks-card/tasks-card.tsx
+++ b/web-console/src/views/home-view/tasks-card/tasks-card.tsx
@@ -20,9 +20,11 @@ import { IconNames } from '@blueprintjs/icons';
 import React from 'react';
 
 import { PluralPairIfNeeded } from '../../../components';
+import { CapacityInfo } from '../../../druid-models';
+import { Capabilities, getClusterCapacity } from '../../../helpers';
 import { useQueryManager } from '../../../hooks';
 import { Api } from '../../../singletons';
-import { Capabilities, lookupBy, pluralIfNeeded, queryDruidSql } from 
'../../../utils';
+import { lookupBy, pluralIfNeeded, queryDruidSql } from '../../../utils';
 import { HomeViewCard } from '../home-view-card/home-view-card';
 
 function getTaskStatus(d: any) {
@@ -30,55 +32,60 @@ function getTaskStatus(d: any) {
 }
 
 export interface TaskCounts {
-  SUCCESS?: number;
-  FAILED?: number;
-  RUNNING?: number;
-  PENDING?: number;
-  WAITING?: number;
+  success?: number;
+  failed?: number;
+  running?: number;
+  pending?: number;
+  waiting?: number;
 }
 
+async function getTaskCounts(capabilities: Capabilities): Promise<TaskCounts> {
+  if (capabilities.hasSql()) {
+    const taskCountsFromQuery = await queryDruidSql<{ status: string; count: 
number }>({
+      query: `SELECT
+  CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS 
"status",
+  COUNT(*) AS "count"
+FROM sys.tasks
+GROUP BY 1`,
+    });
+    return lookupBy(
+      taskCountsFromQuery,
+      x => x.status.toLowerCase(),
+      x => x.count,
+    );
+  } else if (capabilities.hasOverlordAccess()) {
+    const tasks: any[] = (await 
Api.instance.get('/druid/indexer/v1/tasks')).data;
+    return {
+      success: tasks.filter(d => getTaskStatus(d) === 'SUCCESS').length,
+      failed: tasks.filter(d => getTaskStatus(d) === 'FAILED').length,
+      running: tasks.filter(d => getTaskStatus(d) === 'RUNNING').length,
+      pending: tasks.filter(d => getTaskStatus(d) === 'PENDING').length,
+      waiting: tasks.filter(d => getTaskStatus(d) === 'WAITING').length,
+    };
+  } else {
+    throw new Error(`must have SQL or overlord access`);
+  }
+}
+
+export interface TaskCountsAndCapacity extends TaskCounts, 
Partial<CapacityInfo> {}
+
 export interface TasksCardProps {
   capabilities: Capabilities;
 }
 
 export const TasksCard = React.memo(function TasksCard(props: TasksCardProps) {
-  const [taskCountState] = useQueryManager<Capabilities, TaskCounts>({
+  const [cardState] = useQueryManager<Capabilities, TaskCountsAndCapacity>({
     processQuery: async capabilities => {
-      if (capabilities.hasSql()) {
-        const taskCountsFromQuery: { status: string; count: number }[] = await 
queryDruidSql({
-          query: `SELECT
-  CASE WHEN "status" = 'RUNNING' THEN "runner_status" ELSE "status" END AS 
"status",
-  COUNT (*) AS "count"
-FROM sys.tasks
-GROUP BY 1`,
-        });
-        return lookupBy(
-          taskCountsFromQuery,
-          x => x.status,
-          x => x.count,
-        );
-      } else if (capabilities.hasOverlordAccess()) {
-        const tasks: any[] = (await 
Api.instance.get('/druid/indexer/v1/tasks')).data;
-        return {
-          SUCCESS: tasks.filter(d => getTaskStatus(d) === 'SUCCESS').length,
-          FAILED: tasks.filter(d => getTaskStatus(d) === 'FAILED').length,
-          RUNNING: tasks.filter(d => getTaskStatus(d) === 'RUNNING').length,
-          PENDING: tasks.filter(d => getTaskStatus(d) === 'PENDING').length,
-          WAITING: tasks.filter(d => getTaskStatus(d) === 'WAITING').length,
-        };
-      } else {
-        throw new Error(`must have SQL or overlord access`);
-      }
+      const taskCounts = getTaskCounts(capabilities);
+      if (!capabilities.hasOverlordAccess()) return taskCounts;
+
+      const capacity = await getClusterCapacity();
+      return { ...taskCounts, ...capacity };
     },
     initQuery: props.capabilities,
   });
 
-  const taskCounts = taskCountState.data;
-  const successTaskCount = taskCounts ? taskCounts.SUCCESS : 0;
-  const failedTaskCount = taskCounts ? taskCounts.FAILED : 0;
-  const runningTaskCount = taskCounts ? taskCounts.RUNNING : 0;
-  const pendingTaskCount = taskCounts ? taskCounts.PENDING : 0;
-  const waitingTaskCount = taskCounts ? taskCounts.WAITING : 0;
+  const { success, failed, running, pending, waiting, totalTaskSlots } = 
cardState.data || {};
 
   return (
     <HomeViewCard
@@ -86,27 +93,26 @@ GROUP BY 1`,
       href="#ingestion"
       icon={IconNames.GANTT_CHART}
       title="Tasks"
-      loading={taskCountState.loading}
-      error={taskCountState.error}
+      loading={cardState.loading}
+      error={cardState.error}
     >
+      {Boolean(totalTaskSlots) && <p>{pluralIfNeeded(totalTaskSlots || 0, 
'task slot')}</p>}
       <PluralPairIfNeeded
-        firstCount={runningTaskCount}
+        firstCount={running}
         firstSingular="running task"
-        secondCount={pendingTaskCount}
+        secondCount={pending}
         secondSingular="pending task"
       />
-      {Boolean(successTaskCount) && (
-        <p>{pluralIfNeeded(successTaskCount || 0, 'successful task')}</p>
-      )}
-      {Boolean(waitingTaskCount) && <p>{pluralIfNeeded(waitingTaskCount || 0, 
'waiting task')}</p>}
-      {Boolean(failedTaskCount) && <p>{pluralIfNeeded(failedTaskCount || 0, 
'failed task')}</p>}
+      {Boolean(success) && <p>{pluralIfNeeded(success || 0, 'successful 
task')}</p>}
+      {Boolean(waiting) && <p>{pluralIfNeeded(waiting || 0, 'waiting 
task')}</p>}
+      {Boolean(failed) && <p>{pluralIfNeeded(failed || 0, 'failed task')}</p>}
       {!(
-        Boolean(runningTaskCount) ||
-        Boolean(pendingTaskCount) ||
-        Boolean(successTaskCount) ||
-        Boolean(waitingTaskCount) ||
-        Boolean(failedTaskCount)
-      ) && <p>There are no tasks</p>}
+        Boolean(running) ||
+        Boolean(pending) ||
+        Boolean(success) ||
+        Boolean(waiting) ||
+        Boolean(failed)
+      ) && <p>No tasks</p>}
     </HomeViewCard>
   );
 });
diff --git a/web-console/src/views/ingestion-view/ingestion-view.spec.tsx 
b/web-console/src/views/ingestion-view/ingestion-view.spec.tsx
index 45161dbdbb..7c00f7c47b 100644
--- a/web-console/src/views/ingestion-view/ingestion-view.spec.tsx
+++ b/web-console/src/views/ingestion-view/ingestion-view.spec.tsx
@@ -19,7 +19,7 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 
 import { IngestionView } from './ingestion-view';
 
diff --git a/web-console/src/views/ingestion-view/ingestion-view.tsx 
b/web-console/src/views/ingestion-view/ingestion-view.tsx
index e350ad4985..f00c2bd479 100644
--- a/web-console/src/views/ingestion-view/ingestion-view.tsx
+++ b/web-console/src/views/ingestion-view/ingestion-view.tsx
@@ -41,6 +41,7 @@ import {
   TaskTableActionDialog,
 } from '../../dialogs';
 import { QueryWithContext } from '../../druid-models';
+import { Capabilities } from '../../helpers';
 import {
   SMALL_TABLE_PAGE_SIZE,
   SMALL_TABLE_PAGE_SIZE_OPTIONS,
@@ -48,7 +49,6 @@ import {
 } from '../../react-table';
 import { Api, AppToaster } from '../../singletons';
 import {
-  Capabilities,
   deepGet,
   formatDuration,
   getDruidErrorMessage,
diff --git 
a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap 
b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
index 5d273a2869..5cb412b11c 100755
--- 
a/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
+++ 
b/web-console/src/views/segments-view/__snapshots__/segments-view.spec.tsx.snap
@@ -64,9 +64,10 @@ exports[`SegmentsView matches snapshot 1`] = `
             "Num rows",
             "Avg. row size",
             "Replicas",
-            "Is published",
-            "Is realtime",
             "Is available",
+            "Is active",
+            "Is realtime",
+            "Is published",
             "Is overshadowed",
             "Actions",
           ]
@@ -76,6 +77,8 @@ exports[`SegmentsView matches snapshot 1`] = `
         tableColumnsHidden={
           Array [
             "Time span",
+            "Is published",
+            "Is overshadowed",
           ]
         }
       />
@@ -277,10 +280,19 @@ exports[`SegmentsView matches snapshot 1`] = `
           },
           Object {
             "Filter": [Function],
-            "Header": "Is published",
+            "Header": "Is available",
             "accessor": [Function],
             "className": "padded",
-            "id": "is_published",
+            "id": "is_available",
+            "show": true,
+            "width": 100,
+          },
+          Object {
+            "Filter": [Function],
+            "Header": "Is active",
+            "accessor": [Function],
+            "className": "padded",
+            "id": "is_active",
             "show": true,
             "width": 100,
           },
@@ -295,11 +307,11 @@ exports[`SegmentsView matches snapshot 1`] = `
           },
           Object {
             "Filter": [Function],
-            "Header": "Is available",
+            "Header": "Is published",
             "accessor": [Function],
             "className": "padded",
-            "id": "is_available",
-            "show": true,
+            "id": "is_published",
+            "show": false,
             "width": 100,
           },
           Object {
@@ -308,7 +320,7 @@ exports[`SegmentsView matches snapshot 1`] = `
             "accessor": [Function],
             "className": "padded",
             "id": "is_overshadowed",
-            "show": true,
+            "show": false,
             "width": 100,
           },
           Object {
diff --git a/web-console/src/views/segments-view/segments-view.spec.tsx 
b/web-console/src/views/segments-view/segments-view.spec.tsx
index 17a7fb03c5..5f19af2584 100644
--- a/web-console/src/views/segments-view/segments-view.spec.tsx
+++ b/web-console/src/views/segments-view/segments-view.spec.tsx
@@ -19,7 +19,7 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities } from '../../utils';
+import { Capabilities } from '../../helpers';
 import { SegmentsView } from '../segments-view/segments-view';
 
 describe('SegmentsView', () => {
diff --git a/web-console/src/views/segments-view/segments-view.tsx 
b/web-console/src/views/segments-view/segments-view.tsx
index 8b5842bd85..f9917c63c3 100644
--- a/web-console/src/views/segments-view/segments-view.tsx
+++ b/web-console/src/views/segments-view/segments-view.tsx
@@ -42,6 +42,7 @@ import { AsyncActionDialog } from '../../dialogs';
 import { SegmentTableActionDialog } from 
'../../dialogs/segments-table-action-dialog/segment-table-action-dialog';
 import { ShowValueDialog } from 
'../../dialogs/show-value-dialog/show-value-dialog';
 import { QueryWithContext } from '../../druid-models';
+import { Capabilities, CapabilitiesMode } from '../../helpers';
 import {
   booleanCustomTableFilter,
   BooleanFilterInput,
@@ -52,8 +53,6 @@ import {
 } from '../../react-table';
 import { Api } from '../../singletons';
 import {
-  Capabilities,
-  CapabilitiesMode,
   compact,
   deepGet,
   filterMap,
@@ -88,9 +87,10 @@ const tableColumns: Record<CapabilitiesMode, string[]> = {
     'Num rows',
     'Avg. row size',
     'Replicas',
-    'Is published',
-    'Is realtime',
     'Is available',
+    'Is active',
+    'Is realtime',
+    'Is published',
     'Is overshadowed',
     ACTION_COLUMN_LABEL,
   ],
@@ -117,9 +117,10 @@ const tableColumns: Record<CapabilitiesMode, string[]> = {
     'Num rows',
     'Avg. row size',
     'Replicas',
-    'Is published',
-    'Is realtime',
     'Is available',
+    'Is active',
+    'Is realtime',
+    'Is published',
     'Is overshadowed',
   ],
 };
@@ -168,8 +169,9 @@ interface SegmentQueryResultRow {
   avg_row_size: NumberLike;
   num_replicas: number;
   is_available: number;
-  is_published: number;
+  is_active: number;
   is_realtime: number;
+  is_published: number;
   is_overshadowed: number;
 }
 
@@ -212,9 +214,10 @@ END AS "time_span"`,
       visibleColumns.shown('Avg. row size') &&
         `CASE WHEN "num_rows" <> 0 THEN ("size" / "num_rows") ELSE 0 END AS 
"avg_row_size"`,
       visibleColumns.shown('Replicas') && `"num_replicas"`,
-      visibleColumns.shown('Is published') && `"is_published"`,
       visibleColumns.shown('Is available') && `"is_available"`,
+      visibleColumns.shown('Is active') && `"is_active"`,
       visibleColumns.shown('Is realtime') && `"is_realtime"`,
+      visibleColumns.shown('Is published') && `"is_published"`,
       visibleColumns.shown('Is overshadowed') && `"is_overshadowed"`,
     ]);
 
@@ -262,7 +265,7 @@ END AS "time_span"`,
       segmentFilter,
       visibleColumns: new LocalStorageBackedVisibility(
         LocalStorageKeys.SEGMENT_TABLE_COLUMN_SELECTION,
-        ['Time span'],
+        ['Time span', 'Is published', 'Is overshadowed'],
       ),
       groupByInterval: false,
       showSegmentTimeline: false,
@@ -416,8 +419,9 @@ END AS "time_span"`,
                 avg_row_size: -1,
                 num_replicas: -1,
                 is_available: -1,
-                is_published: -1,
+                is_active: -1,
                 is_realtime: -1,
+                is_published: -1,
                 is_overshadowed: -1,
               };
             });
@@ -813,10 +817,19 @@ END AS "time_span"`,
             className: 'padded',
           },
           {
-            Header: 'Is published',
-            show: hasSql && visibleColumns.shown('Is published'),
-            id: 'is_published',
-            accessor: row => String(Boolean(row.is_published)),
+            Header: 'Is available',
+            show: hasSql && visibleColumns.shown('Is available'),
+            id: 'is_available',
+            accessor: row => String(Boolean(row.is_available)),
+            Filter: BooleanFilterInput,
+            className: 'padded',
+            width: 100,
+          },
+          {
+            Header: 'Is active',
+            show: hasSql && visibleColumns.shown('Is active'),
+            id: 'is_active',
+            accessor: row => String(Boolean(row.is_active)),
             Filter: BooleanFilterInput,
             className: 'padded',
             width: 100,
@@ -831,10 +844,10 @@ END AS "time_span"`,
             width: 100,
           },
           {
-            Header: 'Is available',
-            show: hasSql && visibleColumns.shown('Is available'),
-            id: 'is_available',
-            accessor: row => String(Boolean(row.is_available)),
+            Header: 'Is published',
+            show: hasSql && visibleColumns.shown('Is published'),
+            id: 'is_published',
+            accessor: row => String(Boolean(row.is_published)),
             Filter: BooleanFilterInput,
             className: 'padded',
             width: 100,
diff --git a/web-console/src/views/services-view/services-view.spec.tsx 
b/web-console/src/views/services-view/services-view.spec.tsx
index 21d51e9c5d..dc3c678113 100644
--- a/web-console/src/views/services-view/services-view.spec.tsx
+++ b/web-console/src/views/services-view/services-view.spec.tsx
@@ -19,7 +19,8 @@
 import { shallow } from 'enzyme';
 import React from 'react';
 
-import { Capabilities, QueryState } from '../../utils';
+import { Capabilities } from '../../helpers';
+import { QueryState } from '../../utils';
 
 import { ServicesView } from './services-view';
 
diff --git a/web-console/src/views/services-view/services-view.tsx 
b/web-console/src/views/services-view/services-view.tsx
index 9dff93c8ab..6fd7985296 100644
--- a/web-console/src/views/services-view/services-view.tsx
+++ b/web-console/src/views/services-view/services-view.tsx
@@ -35,11 +35,10 @@ import {
 } from '../../components';
 import { AsyncActionDialog } from '../../dialogs';
 import { QueryWithContext } from '../../druid-models';
+import { Capabilities, CapabilitiesMode } from '../../helpers';
 import { STANDARD_TABLE_PAGE_SIZE, STANDARD_TABLE_PAGE_SIZE_OPTIONS } from 
'../../react-table';
 import { Api, AppToaster } from '../../singletons';
 import {
-  Capabilities,
-  CapabilitiesMode,
   deepGet,
   filterMap,
   formatBytes,
diff --git 
a/web-console/src/views/sql-data-loader-view/ingestion-progress-dialog/ingestion-progress-dialog.tsx
 
b/web-console/src/views/sql-data-loader-view/ingestion-progress-dialog/ingestion-progress-dialog.tsx
index 3e911096c7..b7c8480e77 100644
--- 
a/web-console/src/views/sql-data-loader-view/ingestion-progress-dialog/ingestion-progress-dialog.tsx
+++ 
b/web-console/src/views/sql-data-loader-view/ingestion-progress-dialog/ingestion-progress-dialog.tsx
@@ -95,15 +95,22 @@ export const IngestionProgressDialog = React.memo(function 
IngestionProgressDial
       <div className={Classes.DIALOG_FOOTER}>
         <div className={Classes.DIALOG_FOOTER_ACTIONS}>
           {insertResultState.isLoading() && (
-            <Button
-              icon={IconNames.GANTT_CHART}
-              text="Go to Ingestion view"
-              rightIcon={IconNames.ARROW_TOP_RIGHT}
-              onClick={() => {
-                if (!insertResultState.intermediate) return;
-                goToIngestion(insertResultState.intermediate.id);
-              }}
-            />
+            <>
+              <Button
+                icon={IconNames.RESET}
+                text="Ingest in background (reset data loader)"
+                onClick={onReset}
+              />
+              <Button
+                icon={IconNames.GANTT_CHART}
+                text="Go to Ingestion view"
+                rightIcon={IconNames.ARROW_TOP_RIGHT}
+                onClick={() => {
+                  if (!insertResultState.intermediate) return;
+                  goToIngestion(insertResultState.intermediate.id);
+                }}
+              />
+            </>
           )}
           {insertResultState.isError() && <Button text="Close" 
onClick={onClose} />}
           {insertResultState.data && (
diff --git 
a/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx 
b/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx
index 2b3126372a..e2216719dd 100644
--- a/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx
+++ b/web-console/src/views/sql-data-loader-view/sql-data-loader-view.tsx
@@ -26,12 +26,14 @@ import {
   ExternalConfig,
   externalConfigToIngestQueryPattern,
   ingestQueryPatternToQuery,
+  QueryContext,
   QueryWithContext,
 } from '../../druid-models';
-import { submitTaskQuery } from '../../helpers';
+import { Capabilities, maybeGetClusterCapacity, submitTaskQuery } from 
'../../helpers';
 import { useLocalStorageState } from '../../hooks';
 import { AppToaster } from '../../singletons';
 import { deepDelete, LocalStorageKeys } from '../../utils';
+import { CapacityAlert } from 
'../workbench-view/capacity-alert/capacity-alert';
 import { InputFormatStep } from 
'../workbench-view/input-format-step/input-format-step';
 import { InputSourceStep } from 
'../workbench-view/input-source-step/input-source-step';
 import { MaxTasksButton } from 
'../workbench-view/max-tasks-button/max-tasks-button';
@@ -47,6 +49,7 @@ interface LoaderContent extends QueryWithContext {
 }
 
 export interface SqlDataLoaderViewProps {
+  capabilities: Capabilities;
   goToQuery(queryWithContext: QueryWithContext): void;
   goToIngestion(taskId: string): void;
 }
@@ -54,7 +57,8 @@ export interface SqlDataLoaderViewProps {
 export const SqlDataLoaderView = React.memo(function SqlDataLoaderView(
   props: SqlDataLoaderViewProps,
 ) {
-  const { goToQuery, goToIngestion } = props;
+  const { capabilities, goToQuery, goToIngestion } = props;
+  const [alertElement, setAlertElement] = useState<JSX.Element | undefined>();
   const [externalConfigStep, setExternalConfigStep] = 
useState<Partial<ExternalConfig>>({});
   const [content, setContent] = useLocalStorageState<LoaderContent | 
undefined>(
     LocalStorageKeys.SQL_DATA_LOADER_CONTENT,
@@ -75,6 +79,26 @@ export const SqlDataLoaderView = React.memo(function 
SqlDataLoaderView(
     );
   }
 
+  async function submitTask(query: string, context: QueryContext) {
+    if (!content) return;
+
+    try {
+      const execution = await submitTaskQuery({
+        query,
+        context,
+      });
+
+      const taskId = execution instanceof Execution ? execution.id : 
execution.state.id;
+
+      setContent({ ...content, id: taskId });
+    } catch (e) {
+      AppToaster.show({
+        message: `Error submitting task: ${e.message}`,
+        intent: Intent.DANGER,
+      });
+    }
+  }
+
   return (
     <div className="sql-data-loader-view">
       {needVerify ? (
@@ -105,33 +129,47 @@ export const SqlDataLoaderView = React.memo(function 
SqlDataLoaderView(
           goToQuery={() => goToQuery(content)}
           onBack={() => setContent(undefined)}
           onDone={async () => {
-            const ingestDatasource = SqlQuery.parse(content.queryString)
-              .getIngestTable()
-              ?.getName();
+            const { queryString, queryContext } = content;
+            const ingestDatasource = 
SqlQuery.parse(queryString).getIngestTable()?.getName();
 
             if (!ingestDatasource) {
               AppToaster.show({ message: `Must have an ingest datasource`, 
intent: Intent.DANGER });
               return;
             }
 
-            try {
-              const execution = await submitTaskQuery({
-                query: content.queryString,
-                context: content.queryContext,
-              });
+            const clusterCapacity = capabilities.getClusterCapacity();
+            let effectiveContext = queryContext || {};
+            if (
+              typeof effectiveContext.maxNumTasks === 'undefined' &&
+              typeof clusterCapacity === 'number'
+            ) {
+              effectiveContext = { ...effectiveContext, maxNumTasks: 
clusterCapacity };
+            }
 
-              const taskId = execution instanceof Execution ? execution.id : 
execution.state.id;
+            const capacityInfo = await maybeGetClusterCapacity();
 
-              setContent({ ...content, id: taskId });
-            } catch (e) {
-              AppToaster.show({
-                message: `Error submitting task: ${e.message}`,
-                intent: Intent.DANGER,
-              });
+            const effectiveMaxNumTasks = effectiveContext.maxNumTasks ?? 2;
+
+            if (capacityInfo && capacityInfo.availableTaskSlots < 
effectiveMaxNumTasks) {
+              setAlertElement(
+                <CapacityAlert
+                  maxNumTasks={effectiveMaxNumTasks}
+                  capacityInfo={capacityInfo}
+                  onRun={() => {
+                    void submitTask(queryString, effectiveContext);
+                  }}
+                  onClose={() => {
+                    setAlertElement(undefined);
+                  }}
+                />,
+              );
+            } else {
+              await submitTask(queryString, effectiveContext);
             }
           }}
           extraCallout={
             <MaxTasksButton
+              clusterCapacity={capabilities.getClusterCapacity()}
               queryContext={content.queryContext || {}}
               changeQueryContext={queryContext => setContent({ ...content, 
queryContext })}
               minimal
@@ -198,6 +236,7 @@ export const SqlDataLoaderView = React.memo(function 
SqlDataLoaderView(
           onClose={() => setContent(deepDelete(content, 'id'))}
         />
       )}
+      {alertElement}
     </div>
   );
 });
diff --git 
a/web-console/src/views/workbench-view/capacity-alert/capacity-alert.tsx 
b/web-console/src/views/workbench-view/capacity-alert/capacity-alert.tsx
index 7ab23a2d5c..ae786b71ae 100644
--- a/web-console/src/views/workbench-view/capacity-alert/capacity-alert.tsx
+++ b/web-console/src/views/workbench-view/capacity-alert/capacity-alert.tsx
@@ -70,9 +70,9 @@ export function CapacityAlert(props: CapacityAlertProps) {
       >
         <p>
           The cluster does not currently have enough available task slots 
(current usage:{' '}
-          
<Code>{`${formatInteger(usedTaskSlots)}/${formatInteger(totalTaskSlots)}`}</Code>)
 to run
-          this query which is set to use up to 
<Code>{formatInteger(maxNumTasks)}</Code> tasks. This
-          query might have to wait for task slots to free up before running.
+          <Code>{`${formatInteger(usedTaskSlots)} of 
${formatInteger(totalTaskSlots)}`}</Code>) to
+          run this query which is set to use up to 
<Code>{formatInteger(maxNumTasks)}</Code> tasks.
+          This query might have to wait for task slots to free up before 
running.
         </p>
         <p>Are you sure you want to run it?</p>
       </Alert>
diff --git 
a/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
 
b/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
index 68145ebca2..c824afb1fb 100644
--- 
a/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
+++ 
b/web-console/src/views/workbench-view/execution-stages-pane/__snapshots__/execution-stages-pane.spec.tsx.snap
@@ -84,30 +84,30 @@ exports[`ExecutionStagesPane matches snapshot 1`] = `
       Object {
         "Cell": [Function],
         "Header": <React.Fragment>
-          Data processed
+          Rows processed
           <br />
           <i>
-            rows   (size or files)
+            rows   (input files)
           </i>
         </React.Fragment>,
         "accessor": [Function],
         "className": "padded",
-        "id": "data_processed",
-        "width": 220,
+        "id": "rows_processed",
+        "width": 160,
       },
       Object {
         "Cell": [Function],
         "Header": <React.Fragment>
-          Data processing rate
+          Processing rate
           <br />
           <i>
-            rows/s   (data rate)
+            rows/s
           </i>
         </React.Fragment>,
         "accessor": [Function],
         "className": "padded",
-        "id": "data_processing_rate",
-        "width": 200,
+        "id": "processing_rate",
+        "width": 150,
       },
       Object {
         "Header": "Phase",
diff --git 
a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
 
b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
index 6080979cd9..ae49b736d1 100644
--- 
a/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
+++ 
b/web-console/src/views/workbench-view/execution-stages-pane/execution-stages-pane.scss
@@ -71,11 +71,6 @@
       text-align: right;
       padding-right: 15px;
     }
-
-    .sort-percent {
-      display: inline-block;
-      margin-left: 20px;
-    }
   }
 
   .execution-stage-detail-pane {
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 f376c4109a..3732dff785 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
@@ -26,6 +26,7 @@ import ReactTable, { Column } from 'react-table';
 import { BracedText, TableClickableCell } from '../../../components';
 import {
   ChannelCounterName,
+  ChannelFields,
   ClusterBy,
   CounterName,
   Execution,
@@ -40,7 +41,8 @@ import {
   capitalizeFirst,
   clamp,
   deepGet,
-  formatBytes,
+  filterMap,
+  formatBytesCompact,
   formatDuration,
   formatDurationWithMs,
   formatInteger,
@@ -54,6 +56,7 @@ import './execution-stages-pane.scss';
 
 const MAX_STAGE_ROWS = 20;
 const MAX_DETAIL_ROWS = 20;
+const NOT_SIZE_ON_DISK = '(does not represent size on disk)';
 
 function formatBreakdown(breakdown: Record<string, number>): string {
   return Object.keys(breakdown)
@@ -63,8 +66,6 @@ function formatBreakdown(breakdown: Record<string, number>): 
string {
 
 const formatRows = formatInteger;
 const formatRowRate = formatInteger;
-const formatSize = (bytes: number) => `(${formatBytes(bytes)})`;
-const formatByteRate = (byteRate: number) => `(${formatBytes(byteRate)}/s)`;
 const formatFrames = formatInteger;
 const formatDurationDynamic = (n: NumberLike) =>
   n < 1000 ? formatDurationWithMs(n) : formatDuration(n);
@@ -120,9 +121,6 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
   const rowRateValues = stages.stages.map(s =>
     formatRowRate(stages.getRateFromStage(s, 'rows') || 0),
   );
-  const byteRateValues = stages.stages.map(s =>
-    formatByteRate(stages.getRateFromStage(s, 'bytes') || 0),
-  );
 
   const rowsValues = stages.stages.flatMap(stage => [
     ...stages.getInputCountersForStage(stage, 'rows').map(formatRows),
@@ -130,14 +128,10 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
     formatRows(stages.getTotalCounterForStage(stage, 'shuffle', 'rows')),
   ]);
 
-  const bytesAndFilesValues = stages.stages.flatMap(stage => {
+  const filesValues = filterMap(stages.stages, stage => {
     const inputFileCount = stages.getTotalInputForStage(stage, 'totalFiles');
-    return [
-      ...stages.getInputCountersForStage(stage, 'bytes').map(formatSize),
-      formatSize(stages.getTotalCounterForStage(stage, 'output', 'bytes')),
-      formatSize(stages.getTotalCounterForStage(stage, 'shuffle', 'bytes')),
-      inputFileCount ? formatFileOfTotalForBrace(inputFileCount, 
inputFileCount) : '',
-    ];
+    if (!inputFileCount) return;
+    return formatFileOfTotalForBrace(inputFileCount, inputFileCount);
   });
 
   function detailedStats(stage: StageDefinition) {
@@ -164,13 +158,10 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
       bracesRows[counterName] = wideCounters.map(wideCounter =>
         formatRows(wideCounter[counterName]!.rows),
       );
-      bracesExtra[counterName] = wideCounters.map(wideCounter => {
+      bracesExtra[counterName] = filterMap(wideCounters, wideCounter => {
         const totalFiles = wideCounter[counterName]!.totalFiles;
-        if (totalFiles) {
-          return formatFileOfTotalForBrace(totalFiles, totalFiles);
-        } else {
-          return formatSize(wideCounter[counterName]!.bytes);
-        }
+        if (!totalFiles) return;
+        return formatFileOfTotalForBrace(totalFiles, totalFiles);
       });
     }
 
@@ -212,7 +203,7 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
                 ) : (
                   stages.getStageCounterTitle(stage, counterName)
                 ),
-                isInput ? <i>rows &nbsp; (size or files)</i> : <i>rows &nbsp; 
(size)</i>,
+                isInput ? <i>rows &nbsp; (input files)</i> : <i>rows</i>,
               ),
               id: counterName,
               accessor: d => d[counterName]!.rows,
@@ -222,8 +213,16 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
                 const c = (original as SimpleWideCounter)[counterName]!;
                 return (
                   <>
-                    <BracedText text={formatRows(value)} 
braces={bracesRows[counterName]} />
-                    {c.totalFiles ? (
+                    <BracedText
+                      text={formatRows(value)}
+                      braces={bracesRows[counterName]}
+                      title={
+                        c.bytes
+                          ? `Uncompressed size: ${formatBytesCompact(c.bytes)} 
${NOT_SIZE_ON_DISK}`
+                          : undefined
+                      }
+                    />
+                    {Boolean(c.totalFiles) && (
                       <>
                         {' '}
                         &nbsp;{' '}
@@ -232,13 +231,7 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
                           braces={bracesExtra[counterName]}
                         />
                       </>
-                    ) : c.bytes ? (
-                      <>
-                        {' '}
-                        &nbsp;
-                        <BracedText text={formatSize(c.bytes)} 
braces={bracesExtra[counterName]} />
-                      </>
-                    ) : undefined}
+                    )}
                   </>
                 );
               },
@@ -263,14 +256,10 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
     );
 
     const bracesRows: Record<ChannelCounterName, string[]> = {} as any;
-    const bracesBytes: Record<ChannelCounterName, string[]> = {} as any;
     for (const counterName of counterNames) {
       bracesRows[counterName] = wideCounters.map(wideCounter =>
         formatRows(wideCounter[counterName]!.rows),
       );
-      bracesBytes[counterName] = wideCounters.map(wideCounter =>
-        formatSize(wideCounter[counterName]!.bytes),
-      );
     }
 
     return (
@@ -305,18 +294,17 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
               className: 'padded',
               width: 180,
               Cell({ value, original }) {
-                const c = original[counterName];
+                const c: Record<ChannelFields, number> = original[counterName];
                 return (
-                  <>
-                    <BracedText text={formatRows(value)} 
braces={bracesRows[counterName]} />
-                    {c.bytes ? (
-                      <>
-                        {' '}
-                        &nbsp;
-                        <BracedText text={formatSize(c.bytes)} 
braces={bracesBytes[counterName]} />
-                      </>
-                    ) : undefined}
-                  </>
+                  <BracedText
+                    text={formatRows(value)}
+                    braces={bracesRows[counterName]}
+                    title={
+                      c.bytes
+                        ? `Uncompressed size: ${formatBytesCompact(c.bytes)} 
${NOT_SIZE_ON_DISK}`
+                        : undefined
+                    }
+                  />
                 );
               },
             };
@@ -329,11 +317,21 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
   function dataProcessedInput(stage: StageDefinition, inputNumber: number) {
     const inputCounter: CounterName = `input${inputNumber}`;
     if (!stages.hasCounterForStage(stage, inputCounter)) return;
-    const inputSizeBytes = stages.getTotalCounterForStage(stage, inputCounter, 
'bytes');
     const inputFileCount = stages.getTotalCounterForStage(stage, inputCounter, 
'totalFiles');
 
+    const bytes = stages.getTotalCounterForStage(stage, inputCounter, 'bytes');
     return (
-      <div className="data-transfer" key={inputNumber}>
+      <div
+        className="data-transfer"
+        key={inputNumber}
+        title={
+          bytes
+            ? `Input${inputNumber} uncompressed size: ${formatBytesCompact(
+                bytes,
+              )} ${NOT_SIZE_ON_DISK}`
+            : undefined
+        }
+      >
         <BracedText
           text={formatRows(stages.getTotalCounterForStage(stage, inputCounter, 
'rows'))}
           braces={rowsValues}
@@ -347,14 +345,9 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
                 stages.getTotalCounterForStage(stage, inputCounter, 'files'),
                 inputFileCount,
               )}
-              braces={bytesAndFilesValues}
+              braces={filesValues}
             />
           </>
-        ) : inputSizeBytes ? (
-          <>
-            {' '}
-            &nbsp; <BracedText text={formatSize(inputSizeBytes)} 
braces={bytesAndFilesValues} />
-          </>
         ) : undefined}
       </div>
     );
@@ -383,21 +376,20 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
   function dataProcessedOutput(stage: StageDefinition) {
     if (!stages.hasCounterForStage(stage, 'output')) return;
 
+    const title = stages.getStageCounterTitle(stage, 'output');
     return (
       <div
         className="data-transfer"
-        title={`${stages.getStageCounterTitle(stage, 'output')} frames: 
${formatFrames(
+        title={`${title} frames: ${formatFrames(
           stages.getTotalCounterForStage(stage, 'output', 'frames'),
-        )}`}
+        )}
+${title} uncompressed size: ${formatBytesCompact(
+          stages.getTotalCounterForStage(stage, 'output', 'bytes'),
+        )} ${NOT_SIZE_ON_DISK}`}
       >
         <BracedText
           text={formatRows(stages.getTotalCounterForStage(stage, 'output', 
'rows'))}
           braces={rowsValues}
-        />{' '}
-        &nbsp;{' '}
-        <BracedText
-          text={formatSize(stages.getTotalCounterForStage(stage, 'output', 
'bytes'))}
-          braces={bytesAndFilesValues}
         />
       </div>
     );
@@ -410,26 +402,20 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
 
     const shuffleRows = stages.getTotalCounterForStage(stage, 'shuffle', 
'rows');
     const sortProgress = stages.getSortProgressForStage(stage);
+    const title = stages.getStageCounterTitle(stage, 'shuffle');
     return (
       <div
         className="data-transfer"
-        title={`${stages.getStageCounterTitle(stage, 'shuffle')} frames: 
${formatFrames(
+        title={`${title} frames: ${formatFrames(
           stages.getTotalCounterForStage(stage, 'shuffle', 'frames'),
-        )}`}
+        )}
+${title} uncompressed size: ${formatBytesCompact(
+          stages.getTotalCounterForStage(stage, 'shuffle', 'bytes'),
+        )} ${NOT_SIZE_ON_DISK}`}
       >
-        {shuffleRows ? (
-          <>
-            <BracedText text={formatRows(shuffleRows)} braces={rowsValues} /> 
&nbsp;{' '}
-            <BracedText
-              text={formatSize(stages.getTotalCounterForStage(stage, 
'shuffle', 'bytes'))}
-              braces={bytesAndFilesValues}
-            />
-            {0 < sortProgress && sortProgress < 1 && (
-              <div 
className="sort-percent">{`[${formatPercent(sortProgress)}]`}</div>
-            )}
-          </>
-        ) : (
-          <BracedText text={`[${formatPercent(sortProgress)}]`} 
braces={rowsValues} />
+        <BracedText text={shuffleRows ? formatRows(shuffleRows) : ''} 
braces={rowsValues} /> &nbsp;{' '}
+        {0 < sortProgress && sortProgress < 1 && (
+          <> &nbsp;{` ${formatPercent(sortProgress)} sorted`}</>
         )}
       </div>
     );
@@ -535,11 +521,11 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
           },
         },
         {
-          Header: twoLines('Data processed', <i>rows &nbsp; (size or 
files)</i>),
-          id: 'data_processed',
+          Header: twoLines('Rows processed', <i>rows &nbsp; (input files)</i>),
+          id: 'rows_processed',
           accessor: () => null,
           className: 'padded',
-          width: 220,
+          width: 160,
           Cell({ original }) {
             const stage = original as StageDefinition;
             const { input, broadcast } = stage.definition;
@@ -560,26 +546,22 @@ export const ExecutionStagesPane = React.memo(function 
ExecutionStagesPane(
           },
         },
         {
-          Header: twoLines('Data processing rate', <i>rows/s &nbsp; (data 
rate)</i>),
-          id: 'data_processing_rate',
+          Header: twoLines('Processing rate', <i>rows/s</i>),
+          id: 'processing_rate',
           accessor: s => stages.getRateFromStage(s, 'rows'),
           className: 'padded',
-          width: 200,
+          width: 150,
           Cell({ value, original }) {
             const stage = original as StageDefinition;
             if (typeof value !== 'number') return null;
 
             const byteRate = stages.getRateFromStage(stage, 'bytes');
             return (
-              <>
-                <BracedText text={formatRowRate(value)} braces={rowRateValues} 
/>
-                {byteRate ? (
-                  <>
-                    {' '}
-                    &nbsp; <BracedText text={formatByteRate(byteRate)} 
braces={byteRateValues} />
-                  </>
-                ) : undefined}
-              </>
+              <BracedText
+                text={formatRowRate(value)}
+                braces={rowRateValues}
+                title={byteRate ? `${formatBytesCompact(byteRate)}/s` : 
undefined}
+              />
             );
           },
         },
diff --git a/web-console/src/views/workbench-view/helper-query/helper-query.tsx 
b/web-console/src/views/workbench-view/helper-query/helper-query.tsx
index 53049ccdfa..25bca62c4a 100644
--- a/web-console/src/views/workbench-view/helper-query/helper-query.tsx
+++ b/web-console/src/views/workbench-view/helper-query/helper-query.tsx
@@ -77,6 +77,7 @@ export interface HelperQueryProps {
   onDelete(): void;
   onDetails(id: string, initTab?: ExecutionDetailsTab): void;
   queryEngines: DruidEngine[];
+  clusterCapacity: number | undefined;
   goToIngestion(taskId: string): void;
 }
 
@@ -90,6 +91,7 @@ export const HelperQuery = React.memo(function 
HelperQuery(props: HelperQueryPro
     onDelete,
     onDetails,
     queryEngines,
+    clusterCapacity,
     goToIngestion,
   } = props;
   const [alertElement, setAlertElement] = useState<JSX.Element | undefined>();
@@ -252,15 +254,14 @@ export const HelperQuery = React.memo(function 
HelperQuery(props: HelperQueryPro
       return;
     }
 
-    const effectiveQuery = preview ? query.makePreview() : query;
+    const effectiveQuery = preview
+      ? query.makePreview()
+      : query.setMaxNumTasksIfUnset(clusterCapacity);
 
     const capacityInfo = await maybeGetClusterCapacity();
 
     const effectiveMaxNumTasks = effectiveQuery.queryContext.maxNumTasks ?? 2;
-    if (
-      capacityInfo &&
-      capacityInfo.totalTaskSlots - capacityInfo.usedTaskSlots < 
effectiveMaxNumTasks
-    ) {
+    if (capacityInfo && capacityInfo.availableTaskSlots < 
effectiveMaxNumTasks) {
       setAlertElement(
         <CapacityAlert
           maxNumTasks={effectiveMaxNumTasks}
@@ -367,6 +368,7 @@ export const HelperQuery = React.memo(function 
HelperQuery(props: HelperQueryPro
               loading={executionState.loading}
               small
               queryEngines={queryEngines}
+              clusterCapacity={clusterCapacity}
             />
             {executionState.isLoading() && (
               <ExecutionTimerPanel
diff --git 
a/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
 
b/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
index ef1abc9079..70c7278a75 100644
--- 
a/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
+++ 
b/web-console/src/views/workbench-view/max-tasks-button/__snapshots__/max-tasks-button.spec.tsx.snap
@@ -9,127 +9,66 @@ exports[`MaxTasksButton matches snapshot 1`] = `
     content={
       <Blueprint4.Menu>
         <Blueprint4.MenuDivider
-          title="Number of tasks to launch"
+          title="Maximum number of tasks to launch"
         />
         <Blueprint4.MenuItem
           active={false}
           disabled={false}
           icon="tick"
-          label="(1 controller + 1 worker)"
-          multiline={false}
-          onClick={[Function]}
-          popoverProps={Object {}}
-          selected={false}
-          shouldDismissPopover={true}
-          text="2"
-        />
-        <Blueprint4.MenuItem
-          active={false}
-          disabled={false}
-          icon="blank"
-          label="(1 controller + max 2 workers)"
-          multiline={false}
-          onClick={[Function]}
-          popoverProps={Object {}}
-          selected={false}
-          shouldDismissPopover={true}
-          text="3"
-        />
-        <Blueprint4.MenuItem
-          active={false}
-          disabled={false}
-          icon="blank"
-          label="(1 controller + max 3 workers)"
-          multiline={false}
-          onClick={[Function]}
-          popoverProps={Object {}}
-          selected={false}
-          shouldDismissPopover={true}
-          text="4"
-        />
-        <Blueprint4.MenuItem
-          active={false}
-          disabled={false}
-          icon="blank"
-          label="(1 controller + max 4 workers)"
           multiline={false}
           onClick={[Function]}
           popoverProps={Object {}}
           selected={false}
           shouldDismissPopover={true}
-          text="5"
+          text="6 (full cluster capacity)"
         />
         <Blueprint4.MenuItem
           active={false}
           disabled={false}
           icon="blank"
-          label="(1 controller + max 6 workers)"
-          multiline={false}
-          onClick={[Function]}
-          popoverProps={Object {}}
-          selected={false}
-          shouldDismissPopover={true}
-          text="7"
-        />
-        <Blueprint4.MenuItem
-          active={false}
-          disabled={false}
-          icon="blank"
-          label="(1 controller + max 8 workers)"
-          multiline={false}
-          onClick={[Function]}
-          popoverProps={Object {}}
-          selected={false}
-          shouldDismissPopover={true}
-          text="9"
-        />
-        <Blueprint4.MenuItem
-          active={false}
-          disabled={false}
-          icon="blank"
-          label="(1 controller + max 10 workers)"
+          label="(1 controller + 1 worker)"
           multiline={false}
           onClick={[Function]}
           popoverProps={Object {}}
           selected={false}
           shouldDismissPopover={true}
-          text="11"
+          text="2"
         />
         <Blueprint4.MenuItem
           active={false}
           disabled={false}
           icon="blank"
-          label="(1 controller + max 16 workers)"
+          label="(1 controller + max 2 workers)"
           multiline={false}
           onClick={[Function]}
           popoverProps={Object {}}
           selected={false}
           shouldDismissPopover={true}
-          text="17"
+          text="3"
         />
         <Blueprint4.MenuItem
           active={false}
           disabled={false}
           icon="blank"
-          label="(1 controller + max 32 workers)"
+          label="(1 controller + max 3 workers)"
           multiline={false}
           onClick={[Function]}
           popoverProps={Object {}}
           selected={false}
           shouldDismissPopover={true}
-          text="33"
+          text="4"
         />
         <Blueprint4.MenuItem
           active={false}
           disabled={false}
           icon="blank"
-          label="(1 controller + max 64 workers)"
+          label="(1 controller + max 4 workers)"
           multiline={false}
           onClick={[Function]}
           popoverProps={Object {}}
           selected={false}
           shouldDismissPopover={true}
-          text="65"
+          text="5"
         />
         <Blueprint4.MenuItem
           active={false}
@@ -198,7 +137,7 @@ exports[`MaxTasksButton matches snapshot 1`] = `
   >
     <Blueprint4.Button
       rightIcon="caret-down"
-      text="Max tasks: 2"
+      text="Max tasks: 6 (full cluster capacity)"
     />
   </Blueprint4.Popover2>
 </Fragment>
diff --git 
a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.spec.tsx
 
b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.spec.tsx
index 9ab239aacb..719492546c 100644
--- 
a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.spec.tsx
+++ 
b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.spec.tsx
@@ -23,7 +23,9 @@ import { MaxTasksButton } from './max-tasks-button';
 
 describe('MaxTasksButton', () => {
   it('matches snapshot', () => {
-    const comp = shallow(<MaxTasksButton queryContext={{}} 
changeQueryContext={() => {}} />);
+    const comp = shallow(
+      <MaxTasksButton clusterCapacity={6} queryContext={{}} 
changeQueryContext={() => {}} />,
+    );
 
     expect(comp).toMatchSnapshot();
   });
diff --git 
a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx 
b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
index 7391ebaf6e..04c01645d5 100644
--- a/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
+++ b/web-console/src/views/workbench-view/max-tasks-button/max-tasks-button.tsx
@@ -31,7 +31,7 @@ import {
 } from '../../../druid-models';
 import { formatInteger, tickIcon } from '../../../utils';
 
-const MAX_NUM_TASK_OPTIONS = [2, 3, 4, 5, 7, 9, 11, 17, 33, 65];
+const MAX_NUM_TASK_OPTIONS = [2, 3, 4, 5, 7, 9, 11, 17, 33, 65, 129];
 const TASK_ASSIGNMENT_OPTIONS = ['max', 'auto'];
 
 const TASK_ASSIGNMENT_DESCRIPTION: Record<string, string> = {
@@ -40,17 +40,22 @@ const TASK_ASSIGNMENT_DESCRIPTION: Record<string, string> = 
{
 };
 
 export interface MaxTasksButtonProps extends Omit<ButtonProps, 'text' | 
'rightIcon'> {
+  clusterCapacity: number | undefined;
   queryContext: QueryContext;
   changeQueryContext(queryContext: QueryContext): void;
 }
 
 export const MaxTasksButton = function MaxTasksButton(props: 
MaxTasksButtonProps) {
-  const { queryContext, changeQueryContext, ...rest } = props;
+  const { clusterCapacity, queryContext, changeQueryContext, ...rest } = props;
   const [customMaxNumTasksDialogOpen, setCustomMaxNumTasksDialogOpen] = 
useState(false);
 
   const maxNumTasks = getMaxNumTasks(queryContext);
   const taskAssigment = getTaskAssigment(queryContext);
 
+  const fullClusterCapacity = `${clusterCapacity} (full cluster capacity)`;
+  const shownMaxNumTaskOptions = clusterCapacity
+    ? MAX_NUM_TASK_OPTIONS.filter(_ => _ <= clusterCapacity)
+    : MAX_NUM_TASK_OPTIONS;
   return (
     <>
       <Popover2
@@ -58,8 +63,15 @@ export const MaxTasksButton = function MaxTasksButton(props: 
MaxTasksButtonProps
         position={Position.BOTTOM_LEFT}
         content={
           <Menu>
-            <MenuDivider title="Number of tasks to launch" />
-            {MAX_NUM_TASK_OPTIONS.map(m => (
+            <MenuDivider title="Maximum number of tasks to launch" />
+            {Boolean(clusterCapacity) && (
+              <MenuItem
+                icon={tickIcon(typeof maxNumTasks === 'undefined')}
+                text={fullClusterCapacity}
+                onClick={() => 
changeQueryContext(changeMaxNumTasks(queryContext, undefined))}
+              />
+            )}
+            {shownMaxNumTaskOptions.map(m => (
               <MenuItem
                 key={String(m)}
                 icon={tickIcon(m === maxNumTasks)}
@@ -69,7 +81,9 @@ export const MaxTasksButton = function MaxTasksButton(props: 
MaxTasksButtonProps
               />
             ))}
             <MenuItem
-              icon={tickIcon(!MAX_NUM_TASK_OPTIONS.includes(maxNumTasks))}
+              icon={tickIcon(
+                typeof maxNumTasks === 'number' && 
!shownMaxNumTaskOptions.includes(maxNumTasks),
+              )}
               text="Custom"
               onClick={() => setCustomMaxNumTasksDialogOpen(true)}
             />
@@ -88,7 +102,17 @@ export const MaxTasksButton = function 
MaxTasksButton(props: MaxTasksButtonProps
           </Menu>
         }
       >
-        <Button {...rest} text={`Max tasks: ${maxNumTasks}`} 
rightIcon={IconNames.CARET_DOWN} />
+        <Button
+          {...rest}
+          text={`Max tasks: ${
+            typeof maxNumTasks === 'undefined'
+              ? clusterCapacity
+                ? fullClusterCapacity
+                : 2
+              : maxNumTasks
+          }`}
+          rightIcon={IconNames.CARET_DOWN}
+        />
       </Popover2>
       {customMaxNumTasksDialogOpen && (
         <NumericInputDialog
@@ -104,7 +128,7 @@ export const MaxTasksButton = function 
MaxTasksButton(props: MaxTasksButtonProps
           }
           minValue={2}
           integer
-          initValue={maxNumTasks}
+          initValue={maxNumTasks || 2}
           onSubmit={p => {
             changeQueryContext(changeMaxNumTasks(queryContext, p));
           }}
diff --git a/web-console/src/views/workbench-view/query-tab/query-tab.tsx 
b/web-console/src/views/workbench-view/query-tab/query-tab.tsx
index fc05f2db01..a56e9189aa 100644
--- a/web-console/src/views/workbench-view/query-tab/query-tab.tsx
+++ b/web-console/src/views/workbench-view/query-tab/query-tab.tsx
@@ -87,6 +87,7 @@ export interface QueryTabProps {
   onDetails(id: string, initTab?: ExecutionDetailsTab): void;
   queryEngines: DruidEngine[];
   runMoreMenu: JSX.Element;
+  clusterCapacity: number | undefined;
   goToIngestion(taskId: string): void;
 }
 
@@ -100,6 +101,7 @@ export const QueryTab = React.memo(function QueryTab(props: 
QueryTabProps) {
     onDetails,
     queryEngines,
     runMoreMenu,
+    clusterCapacity,
     goToIngestion,
   } = props;
   const [alertElement, setAlertElement] = useState<JSX.Element | undefined>();
@@ -281,15 +283,14 @@ export const QueryTab = React.memo(function 
QueryTab(props: QueryTabProps) {
       return;
     }
 
-    const effectiveQuery = preview ? query.makePreview() : query;
+    const effectiveQuery = preview
+      ? query.makePreview()
+      : query.setMaxNumTasksIfUnset(clusterCapacity);
 
     const capacityInfo = await maybeGetClusterCapacity();
 
     const effectiveMaxNumTasks = effectiveQuery.queryContext.maxNumTasks ?? 2;
-    if (
-      capacityInfo &&
-      capacityInfo.totalTaskSlots - capacityInfo.usedTaskSlots < 
effectiveMaxNumTasks
-    ) {
+    if (capacityInfo && capacityInfo.availableTaskSlots < 
effectiveMaxNumTasks) {
       setAlertElement(
         <CapacityAlert
           maxNumTasks={effectiveMaxNumTasks}
@@ -344,6 +345,7 @@ export const QueryTab = React.memo(function QueryTab(props: 
QueryTabProps) {
                 }}
                 onDetails={onDetails}
                 queryEngines={queryEngines}
+                clusterCapacity={clusterCapacity}
                 goToIngestion={goToIngestion}
               />
             ))}
@@ -404,6 +406,7 @@ export const QueryTab = React.memo(function QueryTab(props: 
QueryTabProps) {
               onRun={handleRun}
               loading={executionState.loading}
               queryEngines={queryEngines}
+              clusterCapacity={clusterCapacity}
               moreMenu={runMoreMenu}
             />
             {executionState.isLoading() && (
diff --git 
a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
 
b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
index d4069fd0ed..e6faced766 100644
--- 
a/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
+++ 
b/web-console/src/views/workbench-view/recent-query-task-panel/recent-query-task-panel.tsx
@@ -27,7 +27,7 @@ import React, { useState } from 'react';
 import { Loader } from '../../../components';
 import { Execution, WorkbenchQuery } from '../../../druid-models';
 import { cancelTaskExecution, getTaskExecution } from '../../../helpers';
-import { useInterval, useQueryManager } from '../../../hooks';
+import { useClock, useInterval, useQueryManager } from '../../../hooks';
 import { AppToaster } from '../../../singletons';
 import { downloadQueryDetailArchive, formatDuration, queryDruidSql } from 
'../../../utils';
 import { CancelQueryDialog } from '../cancel-query-dialog/cancel-query-dialog';
@@ -114,6 +114,8 @@ LIMIT 100`,
     queryManager.rerunLastQuery(true);
   }, 30000);
 
+  const now = useClock();
+
   const incrementWorkVersion = useWorkStateStore(state => state.increment);
 
   const queryTaskHistory = queryTaskHistoryState.getSomeData();
@@ -205,6 +207,11 @@ LIMIT 100`,
               </Menu>
             );
 
+            const duration =
+              w.taskStatus === 'RUNNING'
+                ? now.valueOf() - new Date(w.createdTime).valueOf()
+                : w.duration;
+
             const [icon, color] = statusToIconAndColor(w.taskStatus);
             return (
               <Popover2 className="work-entry" key={w.taskId} position="left" 
content={menu}>
@@ -217,7 +224,7 @@ LIMIT 100`,
                     />
                     <div className="timing">
                       {w.createdTime.replace('T', ' ').replace(/\.\d\d\dZ$/, 
'') +
-                        (w.duration > 0 ? ` (${formatDuration(w.duration)})` : 
'')}
+                        (duration > 0 ? ` (${formatDuration(duration)})` : '')}
                     </div>
                   </div>
                   <div className="line2">
diff --git a/web-console/src/views/workbench-view/run-panel/run-panel.tsx 
b/web-console/src/views/workbench-view/run-panel/run-panel.tsx
index 7299760b46..1f375a3dc2 100644
--- a/web-console/src/views/workbench-view/run-panel/run-panel.tsx
+++ b/web-console/src/views/workbench-view/run-panel/run-panel.tsx
@@ -91,11 +91,13 @@ export interface RunPanelProps {
   small?: boolean;
   onRun(preview: boolean): void;
   queryEngines: DruidEngine[];
+  clusterCapacity: number | undefined;
   moreMenu?: JSX.Element;
 }
 
 export const RunPanel = React.memo(function RunPanel(props: RunPanelProps) {
-  const { query, onQueryChange, onRun, moreMenu, loading, small, queryEngines 
} = props;
+  const { query, onQueryChange, onRun, moreMenu, loading, small, queryEngines, 
clusterCapacity } =
+    props;
   const [editContextDialogOpen, setEditContextDialogOpen] = useState(false);
   const [customTimezoneDialogOpen, setCustomTimezoneDialogOpen] = 
useState(false);
   const [indexSpecDialogSpec, setIndexSpecDialogSpec] = useState<IndexSpec | 
undefined>();
@@ -375,7 +377,11 @@ export const RunPanel = React.memo(function 
RunPanel(props: RunPanelProps) {
             />
           </Popover2>
           {effectiveEngine === 'sql-msq-task' && (
-            <MaxTasksButton queryContext={queryContext} 
changeQueryContext={changeQueryContext} />
+            <MaxTasksButton
+              clusterCapacity={clusterCapacity}
+              queryContext={queryContext}
+              changeQueryContext={changeQueryContext}
+            />
           )}
         </ButtonGroup>
       )}
diff --git a/web-console/src/views/workbench-view/workbench-view.tsx 
b/web-console/src/views/workbench-view/workbench-view.tsx
index 56af602e43..76f155baa7 100644
--- a/web-console/src/views/workbench-view/workbench-view.tsx
+++ b/web-console/src/views/workbench-view/workbench-view.tsx
@@ -33,7 +33,12 @@ import {
   TabEntry,
   WorkbenchQuery,
 } from '../../druid-models';
-import { convertSpecToSql, getSpecDatasourceName, getTaskExecution } from 
'../../helpers';
+import {
+  Capabilities,
+  convertSpecToSql,
+  getSpecDatasourceName,
+  getTaskExecution,
+} from '../../helpers';
 import { getLink } from '../../links';
 import { AppToaster } from '../../singletons';
 import { AceEditorStateCache } from '../../singletons/ace-editor-state-cache';
@@ -80,6 +85,7 @@ function externalDataTabId(tabId: string | undefined): 
boolean {
 }
 
 export interface WorkbenchViewProps {
+  capabilities: Capabilities;
   tabId: string | undefined;
   onTabChange(newTabId: string): void;
   initQueryWithContext: QueryWithContext | undefined;
@@ -628,7 +634,8 @@ export class WorkbenchView extends 
React.PureComponent<WorkbenchViewProps, Workb
   }
 
   private renderCenterPanel() {
-    const { mandatoryQueryContext, queryEngines, allowExplain, goToIngestion } 
= this.props;
+    const { capabilities, mandatoryQueryContext, queryEngines, allowExplain, 
goToIngestion } =
+      this.props;
     const { columnMetadataState } = this.state;
     const currentTabEntry = this.getCurrentTabEntry();
 
@@ -647,6 +654,7 @@ export class WorkbenchView extends 
React.PureComponent<WorkbenchViewProps, Workb
           onQueryTab={this.handleNewTab}
           onDetails={this.handleDetails}
           queryEngines={queryEngines}
+          clusterCapacity={capabilities.getClusterCapacity()}
           goToIngestion={goToIngestion}
           runMoreMenu={
             <Menu>


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

Reply via email to