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

elizabeth pushed a commit to branch chore/cache-slack-channels
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/chore/cache-slack-channels by 
this push:
     new 89543ada1d add refresh button
89543ada1d is described below

commit 89543ada1d9df8b72d589e02659504eacde83ec9
Author: Elizabeth Thompson <[email protected]>
AuthorDate: Wed Mar 5 18:04:21 2025 -0800

    add refresh button
---
 .../alerts/components/NotificationMethod.test.tsx  |  77 +++++++++++++
 .../alerts/components/NotificationMethod.tsx       | 127 ++++++++++++---------
 2 files changed, 153 insertions(+), 51 deletions(-)

diff --git 
a/superset-frontend/src/features/alerts/components/NotificationMethod.test.tsx 
b/superset-frontend/src/features/alerts/components/NotificationMethod.test.tsx
index b0c9186e29..eec2a434f6 100644
--- 
a/superset-frontend/src/features/alerts/components/NotificationMethod.test.tsx
+++ 
b/superset-frontend/src/features/alerts/components/NotificationMethod.test.tsx
@@ -52,6 +52,16 @@ const mockSetting: NotificationSetting = {
 const mockEmailSubject = 'Test Subject';
 const mockDefaultSubject = 'Default Subject';
 
+const mockSettingSlackV2: NotificationSetting = {
+  method: NotificationMethodOption.SlackV2,
+  recipients: 'slack-channel',
+  options: [
+    NotificationMethodOption.Email,
+    NotificationMethodOption.Slack,
+    NotificationMethodOption.SlackV2,
+  ],
+};
+
 describe('NotificationMethod', () => {
   beforeEach(() => {
     jest.clearAllMocks();
@@ -480,4 +490,71 @@ describe('NotificationMethod', () => {
       screen.getByText('Recipients are separated by "," or ";"'),
     ).toBeInTheDocument();
   });
+
+  it('shows the textarea when ff is true, slackChannels fail and slack is 
selected', async () => {
+    window.featureFlags = { [FeatureFlag.AlertReportSlackV2]: true };
+    jest.spyOn(SupersetClient, 'get').mockImplementation(() => {
+      throw new Error('Error fetching Slack channels');
+    });
+
+    render(
+      <NotificationMethod
+        setting={{
+          ...mockSettingSlackV2,
+          method: NotificationMethodOption.Slack,
+        }}
+        index={0}
+        onUpdate={mockOnUpdate}
+        onRemove={mockOnRemove}
+        onInputChange={mockOnInputChange}
+        email_subject={mockEmailSubject}
+        defaultSubject={mockDefaultSubject}
+        setErrorSubject={mockSetErrorSubject}
+      />,
+    );
+
+    expect(
+      screen.getByText('Recipients are separated by "," or ";"'),
+    ).toBeInTheDocument();
+  });
+
+  describe('RefreshLabel functionality', () => {
+    it('should call updateSlackOptions with force true when RefreshLabel is 
clicked', async () => {
+      // Set feature flag so that SlackV2 branch renders RefreshLabel
+      window.featureFlags = { [FeatureFlag.AlertReportSlackV2]: true };
+      // Spy on SupersetClient.get which is called by updateSlackOptions
+      const supersetClientSpy = jest
+        .spyOn(SupersetClient, 'get')
+        .mockImplementation(
+          () =>
+            Promise.resolve({ json: { result: [] } }) as unknown as Promise<
+              Response | JsonResponse | TextResponse
+            >,
+        );
+
+      render(
+        <NotificationMethod
+          setting={mockSettingSlackV2}
+          index={0}
+          onUpdate={mockOnUpdate}
+          onRemove={mockOnRemove}
+          onInputChange={mockOnInputChange}
+          email_subject={mockEmailSubject}
+          defaultSubject={mockDefaultSubject}
+          setErrorSubject={mockSetErrorSubject}
+        />,
+      );
+
+      // Wait for RefreshLabel to be rendered (it may have a tooltip with the 
provided content)
+      const refreshLabel = await waitFor(() =>
+        screen.getByLabelText('refresh'),
+      );
+      // Simulate a click on the RefreshLabel
+      userEvent.click(refreshLabel);
+      // Verify that the SupersetClient.get was called indicating that 
updateSlackOptions executed
+      await waitFor(() => {
+        expect(supersetClientSpy).toHaveBeenCalled();
+      });
+    });
+  });
 });
diff --git 
a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx 
b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx
index 17a78697c9..60797929d1 100644
--- a/superset-frontend/src/features/alerts/components/NotificationMethod.tsx
+++ b/superset-frontend/src/features/alerts/components/NotificationMethod.tsx
@@ -36,6 +36,7 @@ import {
 } from '@superset-ui/core';
 import { Select } from 'src/components';
 import Icons from 'src/components/Icons';
+import RefreshLabel from 'src/components/RefreshLabel';
 import {
   NotificationMethodOption,
   NotificationSetting,
@@ -76,6 +77,9 @@ const StyledNotificationMethod = styled.div`
         margin-left: ${theme.gridUnit * 2}px;
         padding-top: ${theme.gridUnit}px;
       }
+      .anticon {
+        margin-left: ${theme.gridUnit}px;
+      }
     }
 
     .ghost-button {
@@ -248,16 +252,68 @@ export const NotificationMethod: 
FunctionComponent<NotificationMethodProps> = ({
     searchString = '',
     types = [],
     exactMatch = false,
+    force = false,
   }: {
     searchString?: string | undefined;
     types?: string[];
     exactMatch?: boolean | undefined;
+    force?: boolean | undefined;
   } = {}): Promise<JsonResponse> => {
-    const queryString = rison.encode({ searchString, types, exactMatch });
+    const queryString = rison.encode({
+      searchString,
+      types,
+      exactMatch,
+      force,
+    });
     const endpoint = `/api/v1/report/slack_channels/?q=${queryString}`;
     return SupersetClient.get({ endpoint });
   };
 
+  const updateSlackOptions = async ({
+    force,
+  }: {
+    force?: boolean | undefined;
+  } = {}) => {
+    fetchSlackChannels({ types: ['public_channel', 'private_channel'], force })
+      .then(({ json }) => {
+        const { result } = json;
+        const options: SlackOptionsType = mapChannelsToOptions(result);
+
+        setSlackOptions(options);
+
+        if (isFeatureEnabled(FeatureFlag.AlertReportSlackV2)) {
+          // for edit mode, map existing ids to names for display if slack v2
+          // or names to ids if slack v1
+          const [publicOptions, privateOptions] = options;
+          if (
+            method &&
+            [
+              NotificationMethodOption.SlackV2,
+              NotificationMethodOption.Slack,
+            ].includes(method)
+          ) {
+            setSlackRecipients(
+              mapSlackValues({
+                method,
+                recipientValue,
+                slackOptions: [
+                  ...publicOptions.options,
+                  ...privateOptions.options,
+                ],
+              }),
+            );
+          }
+        }
+      })
+      .catch(e => {
+        // Fallback to slack v1 if slack v2 is not compatible
+        setUseSlackV1(true);
+      })
+      .finally(() => {
+        setMethodOptionsLoading(false);
+      });
+  };
+
   useEffect(() => {
     const slackEnabled = options?.some(
       option =>
@@ -265,44 +321,7 @@ export const NotificationMethod: 
FunctionComponent<NotificationMethodProps> = ({
         option === NotificationMethodOption.SlackV2,
     );
     if (slackEnabled && !slackOptions[0]?.options.length) {
-      fetchSlackChannels({ types: ['public_channel', 'private_channel'] })
-        .then(({ json }) => {
-          const { result } = json;
-          const options: SlackOptionsType = mapChannelsToOptions(result);
-
-          setSlackOptions(options);
-
-          if (isFeatureEnabled(FeatureFlag.AlertReportSlackV2)) {
-            // for edit mode, map existing ids to names for display if slack v2
-            // or names to ids if slack v1
-            const [publicOptions, privateOptions] = options;
-            if (
-              method &&
-              [
-                NotificationMethodOption.SlackV2,
-                NotificationMethodOption.Slack,
-              ].includes(method)
-            ) {
-              setSlackRecipients(
-                mapSlackValues({
-                  method,
-                  recipientValue,
-                  slackOptions: [
-                    ...publicOptions.options,
-                    ...privateOptions.options,
-                  ],
-                }),
-              );
-            }
-          }
-        })
-        .catch(e => {
-          // Fallback to slack v1 if slack v2 is not compatible
-          setUseSlackV1(true);
-        })
-        .finally(() => {
-          setMethodOptionsLoading(false);
-        });
+      updateSlackOptions();
     }
   }, []);
 
@@ -518,18 +537,24 @@ export const NotificationMethod: 
FunctionComponent<NotificationMethodProps> = ({
                   </>
                 ) : (
                   // for SlackV2
-                  <Select
-                    ariaLabel={t('Select channels')}
-                    mode="multiple"
-                    name="recipients"
-                    value={slackRecipients}
-                    options={slackOptions}
-                    onChange={onSlackRecipientsChange}
-                    allowClear
-                    data-test="recipients"
-                    allowSelectAll={false}
-                    labelInValue
-                  />
+                  <div className="input-container">
+                    <Select
+                      ariaLabel={t('Select channels')}
+                      mode="multiple"
+                      name="recipients"
+                      value={slackRecipients}
+                      options={slackOptions}
+                      onChange={onSlackRecipientsChange}
+                      allowClear
+                      data-test="recipients"
+                      allowSelectAll={false}
+                      labelInValue
+                    />
+                    <RefreshLabel
+                      onClick={() => updateSlackOptions({ force: true })}
+                      tooltipContent={t('Force refresh Slack channels list')}
+                    />
+                  </div>
                 )}
               </div>
             </StyledInputContainer>

Reply via email to