Sfoglia il codice sorgente

[frontend] Add pubsub topic to hide all open global alerts

Johan Åhlén 1 anno fa
parent
commit
0d9fda498f

+ 37 - 14
desktop/core/src/desktop/js/reactComponents/AlertComponent/AlertComponent.test.tsx

@@ -20,14 +20,20 @@ import '@testing-library/jest-dom';
 import userEvent from '@testing-library/user-event';
 import huePubSub from '../../utils/huePubSub';
 
-import AlertComponent from './AlertComponent';
+import AlertComponent, { HueAlert } from './AlertComponent';
+import {
+  GLOBAL_ERROR_TOPIC,
+  GLOBAL_INFO_TOPIC,
+  GLOBAL_WARNING_TOPIC,
+  HIDE_GLOBAL_ALERTS_TOPIC
+} from './events';
 
 describe('AlertComponent', () => {
   it('should show a global error message', async () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
-    act(() => huePubSub.publish('hue.global.error', { message: 'Some error' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Some error' }));
 
     const alerts = screen.queryAllByRole('alert');
     expect(alerts).toHaveLength(1);
@@ -38,8 +44,8 @@ describe('AlertComponent', () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 1' }));
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 2' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 1' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 2' }));
 
     const alerts = screen.queryAllByRole('alert');
     expect(alerts).toHaveLength(2);
@@ -51,7 +57,7 @@ describe('AlertComponent', () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
-    act(() => huePubSub.publish('hue.global.error', { message: '' }));
+    act(() => huePubSub.publish<HueAlert>('hue.global.error', { message: '' }));
 
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
   });
@@ -60,9 +66,9 @@ describe('AlertComponent', () => {
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
 
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 1' }));
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 2' }));
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 1' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 1' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 2' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 1' }));
 
     const alerts = screen.queryAllByRole('alert');
     expect(alerts).toHaveLength(2);
@@ -74,9 +80,9 @@ describe('AlertComponent', () => {
     const user = userEvent.setup();
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 1' }));
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 2' }));
-    act(() => huePubSub.publish('hue.global.error', { message: 'Error 3' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 1' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 2' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Error 3' }));
 
     // Closing "Error 2"
     const initialAlerts = screen.queryAllByRole('alert');
@@ -108,7 +114,7 @@ describe('AlertComponent', () => {
     jest.useFakeTimers();
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
-    act(() => huePubSub.publish('hue.global.info', { message: 'info' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_INFO_TOPIC, { message: 'info' }));
     expect(screen.queryAllByRole('alert')).toHaveLength(1);
 
     expectAlertToBeGoneAfterThreeSeconds();
@@ -120,7 +126,9 @@ describe('AlertComponent', () => {
     jest.useFakeTimers();
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
-    act(() => huePubSub.publish('hue.global.warning', { message: 'Some warning', noStick: true }));
+    act(() =>
+      huePubSub.publish<HueAlert>(GLOBAL_WARNING_TOPIC, { message: 'Some warning', noStick: true })
+    );
     expect(screen.queryAllByRole('alert')).toHaveLength(1);
 
     expectAlertToBeGoneAfterThreeSeconds();
@@ -132,11 +140,26 @@ describe('AlertComponent', () => {
     jest.useFakeTimers();
     render(<AlertComponent />);
     expect(screen.queryAllByRole('alert')).toHaveLength(0);
-    act(() => huePubSub.publish('hue.global.error', { message: 'Some error', noStick: true }));
+    act(() =>
+      huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Some error', noStick: true })
+    );
     expect(screen.queryAllByRole('alert')).toHaveLength(1);
 
     expectAlertToBeGoneAfterThreeSeconds();
 
     jest.useRealTimers();
   });
+
+  it('should close all alerts "hide.global.alerts" is published', async () => {
+    render(<AlertComponent />);
+    expect(screen.queryAllByRole('alert')).toHaveLength(0);
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_INFO_TOPIC, { message: 'Some info' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_WARNING_TOPIC, { message: 'Some warning' }));
+    act(() => huePubSub.publish<HueAlert>(GLOBAL_ERROR_TOPIC, { message: 'Some error' }));
+    expect(screen.queryAllByRole('alert')).toHaveLength(3);
+
+    act(() => huePubSub.publish(HIDE_GLOBAL_ALERTS_TOPIC));
+
+    expect(screen.queryAllByRole('alert')).toHaveLength(0);
+  });
 });

+ 40 - 27
desktop/core/src/desktop/js/reactComponents/AlertComponent/AlertComponent.tsx

@@ -19,22 +19,32 @@ import { AlertProps } from 'antd/lib/alert';
 import Alert from 'cuix/dist/components/Alert/Alert';
 
 import './AlertComponent.scss';
-import { i18nReact } from '../../utils/i18nReact';
+import {
+  GLOBAL_ERROR_TOPIC,
+  GLOBAL_INFO_TOPIC,
+  GLOBAL_WARNING_TOPIC,
+  HIDE_GLOBAL_ALERTS_TOPIC
+} from './events';
+import { HueAlert } from './types';
 import { useHuePubSub } from '../useHuePubSub';
-
-interface HueAlert {
-  message: string;
-  noStick: boolean;
-}
+import { i18nReact } from 'utils/i18nReact';
 
 type alertType = AlertProps['type'];
 
 interface VisibleAlert {
   alert: HueAlert;
   type: alertType;
+  timeoutHandle?: number;
 }
+
+const clearCloseTimeout = (alert: VisibleAlert) => {
+  if (alert.timeoutHandle) {
+    clearTimeout(alert.timeoutHandle);
+  }
+};
+
 const AlertComponent: React.FC = () => {
-  const [alert, setAlerts] = useState<VisibleAlert[]>([]);
+  const [alerts, setAlerts] = useState<VisibleAlert[]>([]);
   const updateAlerts = (alert: HueAlert, type: alertType) => {
     if (!alert.message) {
       return;
@@ -49,7 +59,7 @@ const AlertComponent: React.FC = () => {
       const newAlert: VisibleAlert = { alert, type };
 
       if (type === 'info' || alert.noStick) {
-        setTimeout(() => {
+        newAlert.timeoutHandle = setTimeout(() => {
           handleClose(newAlert);
         }, 3000);
       }
@@ -59,28 +69,31 @@ const AlertComponent: React.FC = () => {
   };
 
   useHuePubSub<HueAlert>({
-    topic: 'hue.global.error',
-    callback: newAlert => {
-      updateAlerts(newAlert, 'error');
-    }
+    topic: GLOBAL_ERROR_TOPIC,
+    callback: newAlert => updateAlerts(newAlert, 'error')
   });
 
   useHuePubSub<HueAlert>({
-    topic: 'hue.global.info',
-    callback: newAlert => {
-      updateAlerts(newAlert, 'info');
-    }
+    topic: GLOBAL_INFO_TOPIC,
+    callback: newAlert => updateAlerts(newAlert, 'info')
   });
 
   useHuePubSub<HueAlert>({
-    topic: 'hue.global.warning',
-    callback: newAlert => {
-      updateAlerts(newAlert, 'warning');
+    topic: GLOBAL_WARNING_TOPIC,
+    callback: newAlert => updateAlerts(newAlert, 'warning')
+  });
+
+  useHuePubSub<void>({
+    topic: HIDE_GLOBAL_ALERTS_TOPIC,
+    callback: () => {
+      alerts.forEach(visibleAlert => clearCloseTimeout(visibleAlert));
+      setAlerts([]);
     }
   });
 
-  const handleClose = (alertObjToClose: VisibleAlert) => {
-    const filteredAlerts = alert.filter(alertObj => alertObj !== alertObjToClose);
+  const handleClose = (alertToClose: VisibleAlert) => {
+    const filteredAlerts = alerts.filter(alert => alert !== alertToClose);
+    clearCloseTimeout(alertToClose);
     setAlerts(filteredAlerts);
   };
 
@@ -98,15 +111,15 @@ const AlertComponent: React.FC = () => {
 
   return (
     <div className="hue-alert flash-messages cuix antd">
-      {alert.map(alertObj => (
+      {alerts.map(visibleAlert => (
         <Alert
-          key={alertObj.alert.message}
-          type={alertObj.type}
-          message={getHeader(alertObj)}
-          description={alertObj.alert.message}
+          key={visibleAlert.alert.message}
+          type={visibleAlert.type}
+          message={getHeader(visibleAlert)}
+          description={visibleAlert.alert.message}
           showIcon={true}
           closable={true}
-          onClose={() => handleClose(alertObj)}
+          onClose={() => handleClose(visibleAlert)}
         />
       ))}
     </div>

+ 20 - 0
desktop/core/src/desktop/js/reactComponents/AlertComponent/events.ts

@@ -0,0 +1,20 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+export const GLOBAL_INFO_TOPIC = 'hue.global.info';
+export const GLOBAL_WARNING_TOPIC = 'hue.global.warning';
+export const GLOBAL_ERROR_TOPIC = 'hue.global.error';
+export const HIDE_GLOBAL_ALERTS_TOPIC = 'hide.global.alerts';

+ 20 - 0
desktop/core/src/desktop/js/reactComponents/AlertComponent/types.ts

@@ -0,0 +1,20 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// 'License'); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an 'AS IS' BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+export interface HueAlert {
+  message: string;
+  noStick?: boolean;
+}