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 2d8eb117c0 Web console: add a button to get out of restricted mode, 
make capability detection more robust (#12503)
2d8eb117c0 is described below

commit 2d8eb117c0bc59aa79016ed0e28519f6b7aa7afc
Author: Vadim Ogievetsky <[email protected]>
AuthorDate: Thu May 5 15:06:59 2022 -0700

    Web console: add a button to get out of restricted mode, make capability 
detection more robust (#12503)
    
    * allow unrestrict
    
    * update tests
---
 .../__snapshots__/header-bar.spec.tsx.snap         |  1 +
 .../src/components/header-bar/header-bar.spec.tsx  |  4 +-
 .../src/components/header-bar/header-bar.tsx       | 39 ++++++++++++++----
 web-console/src/console-application.tsx            | 14 +++++--
 web-console/src/utils/capabilities.ts              | 48 +++++++++++++++++-----
 5 files changed, 84 insertions(+), 22 deletions(-)

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 bfd6aa4c3d..b3943e556a 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
@@ -76,6 +76,7 @@ exports[`HeaderBar matches snapshot 1`] = `
           "queryType": "nativeAndSql",
         }
       }
+      onUnrestrict={[Function]}
     />
     <Blueprint4.Popover2
       boundary="clippingParents"
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 cac4febb26..61650aa7aa 100644
--- a/web-console/src/components/header-bar/header-bar.spec.tsx
+++ b/web-console/src/components/header-bar/header-bar.spec.tsx
@@ -25,7 +25,9 @@ import { HeaderBar } from './header-bar';
 
 describe('HeaderBar', () => {
   it('matches snapshot', () => {
-    const headerBar = shallow(<HeaderBar active="load-data" 
capabilities={Capabilities.FULL} />);
+    const headerBar = shallow(
+      <HeaderBar active="load-data" capabilities={Capabilities.FULL} 
onUnrestrict={() => {}} />,
+    );
     expect(headerBar).toMatchSnapshot();
   });
 });
diff --git a/web-console/src/components/header-bar/header-bar.tsx 
b/web-console/src/components/header-bar/header-bar.tsx
index 217ff80757..53042d91a2 100644
--- a/web-console/src/components/header-bar/header-bar.tsx
+++ b/web-console/src/components/header-bar/header-bar.tsx
@@ -85,10 +85,11 @@ const DruidLogo = React.memo(function DruidLogo() {
 
 interface RestrictedModeProps {
   capabilities: Capabilities;
+  onUnrestrict(capabilities: Capabilities): void;
 }
 
 const RestrictedMode = React.memo(function RestrictedMode(props: 
RestrictedModeProps) {
-  const { capabilities } = props;
+  const { capabilities, onUnrestrict } = props;
   const mode = capabilities.getModeExtended();
 
   let label: string;
@@ -136,7 +137,8 @@ const RestrictedMode = React.memo(function 
RestrictedMode(props: RestrictedModeP
         <p>
           It appears that you are accessing the console on the 
Coordinator/Overlord shared service.
           Due to the lack of access to some APIs on this service the console 
will operate in a
-          limited mode. The full version of the console can be accessed on the 
Router service.
+          limited mode. The unrestricted version of the console can be 
accessed on the Router
+          service.
         </p>
       );
       break;
@@ -157,8 +159,8 @@ const RestrictedMode = React.memo(function 
RestrictedMode(props: RestrictedModeP
       message = (
         <p>
           It appears that you are accessing the console on the Overlord 
service. Due to the lack of
-          access to some APIs on this service the console will operate in a 
limited mode. The full
-          version of the console can be accessed on the Router service.
+          access to some APIs on this service the console will operate in a 
limited mode. The
+          unrestricted version of the console can be accessed on the Router 
service.
         </p>
       );
       break;
@@ -168,7 +170,8 @@ const RestrictedMode = React.memo(function 
RestrictedMode(props: RestrictedModeP
       message = (
         <p>
           Due to the lack of access to some APIs on this service the console 
will operate in a
-          limited mode. The full version of the console can be accessed on the 
Router service.
+          limited mode. The unrestricted version of the console can be 
accessed on the Router
+          service.
         </p>
       );
       break;
@@ -187,6 +190,27 @@ const RestrictedMode = React.memo(function 
RestrictedMode(props: RestrictedModeP
             </ExternalLink>
             .
           </p>
+          <p>
+            It is possible that there is an issue with the capability 
detection. You can enable the
+            unrestricted console but certain features might not work if the 
underlying APIs are not
+            available.
+          </p>
+          <p>
+            <Button
+              icon={IconNames.WARNING_SIGN}
+              text={`Temporarily unrestrict console${capabilities.hasSql() ? 
'' : ' (with SQL)'}`}
+              onClick={() => onUnrestrict(Capabilities.FULL)}
+            />
+          </p>
+          {!capabilities.hasSql() && (
+            <p>
+              <Button
+                icon={IconNames.WARNING_SIGN}
+                text="Temporarily unrestrict console (without SQL)"
+                onClick={() => onUnrestrict(Capabilities.NO_SQL)}
+              />
+            </p>
+          )}
         </PopoverText>
       }
       position={Position.BOTTOM_RIGHT}
@@ -199,10 +223,11 @@ const RestrictedMode = React.memo(function 
RestrictedMode(props: RestrictedModeP
 export interface HeaderBarProps {
   active: HeaderActiveTab;
   capabilities: Capabilities;
+  onUnrestrict(capabilities: Capabilities): void;
 }
 
 export const HeaderBar = React.memo(function HeaderBar(props: HeaderBarProps) {
-  const { active, capabilities } = props;
+  const { active, capabilities, onUnrestrict } = props;
   const [aboutDialogOpen, setAboutDialogOpen] = useState(false);
   const [doctorDialogOpen, setDoctorDialogOpen] = useState(false);
   const [coordinatorDynamicConfigDialogOpen, 
setCoordinatorDynamicConfigDialogOpen] =
@@ -371,7 +396,7 @@ export const HeaderBar = React.memo(function 
HeaderBar(props: HeaderBarProps) {
         />
       </NavbarGroup>
       <NavbarGroup align={Alignment.RIGHT}>
-        <RestrictedMode capabilities={capabilities} />
+        <RestrictedMode capabilities={capabilities} 
onUnrestrict={onUnrestrict} />
         <Popover2 content={configMenu} position={Position.BOTTOM_RIGHT}>
           <Button minimal icon={IconNames.COG} />
         </Popover2>
diff --git a/web-console/src/console-application.tsx 
b/web-console/src/console-application.tsx
index 2283deac68..731002632c 100644
--- a/web-console/src/console-application.tsx
+++ b/web-console/src/console-application.tsx
@@ -55,7 +55,7 @@ export class ConsoleApplication extends React.PureComponent<
 > {
   private readonly capabilitiesQueryManager: QueryManager<null, Capabilities>;
 
-  static shownNotifications() {
+  static shownServiceNotification() {
     AppToaster.show({
       icon: IconNames.ERROR,
       intent: Intent.DANGER,
@@ -87,7 +87,7 @@ export class ConsoleApplication extends React.PureComponent<
     this.capabilitiesQueryManager = new QueryManager({
       processQuery: async () => {
         const capabilities = await Capabilities.detectCapabilities();
-        if (!capabilities) ConsoleApplication.shownNotifications();
+        if (!capabilities) ConsoleApplication.shownServiceNotification();
         return capabilities || Capabilities.FULL;
       },
       onStateChange: ({ data, loading }) => {
@@ -107,6 +107,10 @@ export class ConsoleApplication extends 
React.PureComponent<
     this.capabilitiesQueryManager.terminate();
   }
 
+  private readonly handleUnrestrict = (capabilities: Capabilities) => {
+    this.setState({ capabilities });
+  };
+
   private resetInitialsWithDelay() {
     setTimeout(() => {
       this.taskId = undefined;
@@ -168,7 +172,11 @@ export class ConsoleApplication extends 
React.PureComponent<
 
     return (
       <>
-        <HeaderBar active={active} capabilities={capabilities} />
+        <HeaderBar
+          active={active}
+          capabilities={capabilities}
+          onUnrestrict={this.handleUnrestrict}
+        />
         <div className={classNames('view-container', classType)}>{el}</div>
       </>
     );
diff --git a/web-console/src/utils/capabilities.ts 
b/web-console/src/utils/capabilities.ts
index 406115dfc9..033b398425 100644
--- a/web-console/src/utils/capabilities.ts
+++ b/web-console/src/utils/capabilities.ts
@@ -40,8 +40,9 @@ export interface CapabilitiesOptions {
 }
 
 export class Capabilities {
-  static STATUS_TIMEOUT = 2000;
+  static STATUS_TIMEOUT = 15000;
   static FULL: Capabilities;
+  static NO_SQL: Capabilities;
   static COORDINATOR_OVERLORD: Capabilities;
   static COORDINATOR: Capabilities;
   static OVERLORD: Capabilities;
@@ -55,7 +56,7 @@ export class Capabilities {
     // Check SQL endpoint
     try {
       await Api.instance.post(
-        '/druid/v2/sql',
+        '/druid/v2/sql?capabilities',
         { query: 'SELECT 1337', context: { timeout: 
Capabilities.STATUS_TIMEOUT } },
         { timeout: Capabilities.STATUS_TIMEOUT },
       );
@@ -65,7 +66,7 @@ export class Capabilities {
         return; // other failure
       }
       try {
-        await Api.instance.get('/status', { timeout: 
Capabilities.STATUS_TIMEOUT });
+        await Api.instance.get('/status?capabilities', { timeout: 
Capabilities.STATUS_TIMEOUT });
       } catch (e) {
         return; // total failure
       }
@@ -73,7 +74,7 @@ export class Capabilities {
 
       try {
         await Api.instance.post(
-          '/druid/v2',
+          '/druid/v2?capabilities',
           {
             queryType: 'dataSourceMetadata',
             dataSource: '__web_console_probe__',
@@ -95,9 +96,9 @@ export class Capabilities {
     return 'nativeAndSql';
   }
 
-  static async detectNode(node: 'coordinator' | 'overlord'): Promise<boolean | 
undefined> {
+  static async detectManagementProxy(): Promise<boolean> {
     try {
-      await Api.instance.get(`/proxy/${node}/status`, {
+      await Api.instance.get(`/proxy/coordinator/status?capabilities`, {
         timeout: Capabilities.STATUS_TIMEOUT,
       });
     } catch (e) {
@@ -107,6 +108,21 @@ export class Capabilities {
     return true;
   }
 
+  static async detectNode(node: 'coordinator' | 'overlord'): Promise<boolean> {
+    try {
+      await Api.instance.get(
+        `/druid/${node === 'overlord' ? 'indexer' : 
node}/v1/isLeader?capabilities`,
+        {
+          timeout: Capabilities.STATUS_TIMEOUT,
+        },
+      );
+    } catch (e) {
+      return false;
+    }
+
+    return true;
+  }
+
   static async detectCapabilities(): Promise<Capabilities | undefined> {
     const capabilitiesOverride = 
localStorageGetJson(LocalStorageKeys.CAPABILITIES_OVERRIDE);
     if (capabilitiesOverride) return new Capabilities(capabilitiesOverride);
@@ -114,11 +130,16 @@ export class Capabilities {
     const queryType = await Capabilities.detectQueryType();
     if (typeof queryType === 'undefined') return;
 
-    const coordinator = await Capabilities.detectNode('coordinator');
-    if (typeof coordinator === 'undefined') return;
-
-    const overlord = await Capabilities.detectNode('overlord');
-    if (typeof overlord === 'undefined') return;
+    let coordinator: boolean;
+    let overlord: boolean;
+    if (queryType === 'none') {
+      // must not be running on the router, figure out what node the console 
is on (or both?)
+      coordinator = await Capabilities.detectNode('coordinator');
+      overlord = await Capabilities.detectNode('overlord');
+    } else {
+      // must be running on the router, figure out if the management proxy is 
working
+      coordinator = overlord = await Capabilities.detectManagementProxy();
+    }
 
     return new Capabilities({
       queryType,
@@ -204,6 +225,11 @@ Capabilities.FULL = new Capabilities({
   coordinator: true,
   overlord: true,
 });
+Capabilities.NO_SQL = new Capabilities({
+  queryType: 'nativeOnly',
+  coordinator: true,
+  overlord: true,
+});
 Capabilities.COORDINATOR_OVERLORD = new Capabilities({
   queryType: 'none',
   coordinator: true,


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

Reply via email to