Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions superset-frontend/src/components/RefreshLabel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@ import Icons, { IconType } from 'src/components/Icons';
export interface RefreshLabelProps {
onClick: MouseEventHandler<HTMLSpanElement>;
tooltipContent: string;
disabled?: boolean;
}

const RefreshLabel = ({ onClick, tooltipContent }: RefreshLabelProps) => {
const RefreshLabel = ({
onClick,
tooltipContent,
disabled,
}: RefreshLabelProps) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const IconWithoutRef = forwardRef((props: IconType, ref: any) => (
<Icons.Refresh {...props} />
Expand All @@ -36,7 +41,7 @@ const RefreshLabel = ({ onClick, tooltipContent }: RefreshLabelProps) => {
<Tooltip title={tooltipContent}>
<IconWithoutRef
role="button"
onClick={onClick}
onClick={disabled ? undefined : onClick}
css={(theme: SupersetTheme) => ({
cursor: 'pointer',
color: theme.colors.grayscale.base,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -221,6 +225,8 @@ export const NotificationMethod: FunctionComponent<NotificationMethodProps> = ({
]);

const [useSlackV1, setUseSlackV1] = useState<boolean>(false);
const [isSlackChannelsLoading, setIsSlackChannelsLoading] =
useState<boolean>(true);

const onMethodChange = (selected: {
label: string;
Expand Down Expand Up @@ -248,61 +254,78 @@ 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;
} = {}) => {
setIsSlackChannelsLoading(true);
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);
setIsSlackChannelsLoading(false);
});
};

useEffect(() => {
const slackEnabled = options?.some(
option =>
option === NotificationMethodOption.Slack ||
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();
}
}, []);

Expand Down Expand Up @@ -518,18 +541,26 @@ 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"
loading={isSlackChannelsLoading}
allowSelectAll={false}
labelInValue
/>
<RefreshLabel
onClick={() => updateSlackOptions({ force: true })}
tooltipContent={t('Force refresh Slack channels list')}
disabled={isSlackChannelsLoading}
/>
</div>
)}
</div>
</StyledInputContainer>
Expand Down
6 changes: 5 additions & 1 deletion superset/reports/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,8 +577,12 @@ def slack_channels(self, **kwargs: Any) -> Response:
search_string = params.get("search_string")
types = params.get("types", [])
exact_match = params.get("exact_match", False)
force = params.get("force", False)
channels = get_channels_with_search(
search_string=search_string, types=types, exact_match=exact_match
search_string=search_string,
types=types,
exact_match=exact_match,
force=force,
)
return self.response(200, result=channels)
except SupersetException as ex:
Expand Down
Loading
Loading