Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add 'create channel' option in channels list and refetch alert channels on opening the channels dropdown #6416

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
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
20 changes: 13 additions & 7 deletions frontend/src/container/FormAlertRules/BasicInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
Expand Down Expand Up @@ -83,16 +83,22 @@ function BasicInfo({
window.open(ROUTES.CHANNELS_NEW, '_blank');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const hasLoggedEvent = useRef(false);

useEffect(() => {
if (!channels.loading && isNewRule) {
if (!channels.loading && isNewRule && !hasLoggedEvent.current) {
logEvent('Alert: New alert creation page visited', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
numberOfChannels: channels?.payload?.length,
});
hasLoggedEvent.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channels.payload, channels.loading]);
}, [channels.loading]);

const refetchChannels = async (): Promise<void> => {
await channels.refetch();
};

return (
<>
Expand Down Expand Up @@ -197,7 +203,7 @@ function BasicInfo({
{!shouldBroadCastToAllChannels && (
<Tooltip
title={
noChannels
noChannels && !addNewChannelPermission
? 'No channels. Ask an admin to create a notification channel'
: undefined
}
Expand All @@ -212,10 +218,10 @@ function BasicInfo({
]}
>
<ChannelSelect
disabled={
shouldBroadCastToAllChannels || noChannels || !!channels.loading
}
onDropdownOpen={refetchChannels}
disabled={shouldBroadCastToAllChannels}
currentValue={alertDef.preferredChannels}
handleCreateNewChannels={handleCreateNewChannels}
channels={channels}
onSelectChannels={(preferredChannels): void => {
setAlertDef({
Expand Down
42 changes: 40 additions & 2 deletions frontend/src/container/FormAlertRules/ChannelSelect/index.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,44 @@
import { Select } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { Select, Spin } from 'antd';
import useComponentPermission from 'hooks/useComponentPermission';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { PayloadProps } from 'types/api/channels/getAll';
import AppReducer from 'types/reducer/app';

import { StyledSelect } from './styles';
import { StyledCreateChannelOption, StyledSelect } from './styles';

export interface ChannelSelectProps {
disabled?: boolean;
currentValue?: string[];
onSelectChannels: (s: string[]) => void;
onDropdownOpen: () => void;
channels: State<PayloadProps | undefined>;
handleCreateNewChannels: () => void;
}
ahmadshaheer marked this conversation as resolved.
Show resolved Hide resolved

function ChannelSelect({
disabled,
currentValue,
onSelectChannels,
onDropdownOpen,
channels,
handleCreateNewChannels,
}: ChannelSelectProps): JSX.Element | null {
// init namespace for translations
const { t } = useTranslation('alerts');

const { notifications } = useNotifications();

const handleChange = (value: string[]): void => {
if (value.includes('add-new-channel')) {
handleCreateNewChannels();
return;
}
onSelectChannels(value);
};

Expand All @@ -35,9 +48,27 @@ function ChannelSelect({
description: channels.errorMessage,
});
}

const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const [addNewChannelPermission] = useComponentPermission(
['add_new_channel'],
role,
);

const renderOptions = (): ReactNode[] => {
const children: ReactNode[] = [];

if (!channels.loading && addNewChannelPermission) {
children.push(
<Select.Option key="add-new-channel" value="add-new-channel">
<StyledCreateChannelOption>
<PlusOutlined />
Create a new channel
</StyledCreateChannelOption>
</Select.Option>,
);
}

if (
channels.loading ||
channels.payload === undefined ||
Expand All @@ -56,6 +87,7 @@ function ChannelSelect({

return children;
};

return (
<StyledSelect
disabled={disabled}
Expand All @@ -65,6 +97,12 @@ function ChannelSelect({
placeholder={t('placeholder_channel_select')}
data-testid="alert-channel-select"
value={currentValue}
notFoundContent={channels.loading && <Spin size="small" />}
onDropdownVisibleChange={(open): void => {
if (open) {
onDropdownOpen();
}
}}
onChange={(value): void => {
handleChange(value as string[]);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ import styled from 'styled-components';
export const StyledSelect = styled(Select)`
border-radius: 4px;
`;

export const StyledCreateChannelOption = styled.div`
color: var(--bg-robin-500);
display: flex;
align-items: center;
gap: 8px;
`;
64 changes: 29 additions & 35 deletions frontend/src/hooks/useFetch.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { ErrorResponse, SuccessResponse } from 'types/api';

function useFetch<PayloadProps, FunctionParams>(
Expand All @@ -10,7 +10,7 @@ function useFetch<PayloadProps, FunctionParams>(
(arg0: any): Promise<SuccessResponse<PayloadProps> | ErrorResponse>;
},
param?: FunctionParams,
): State<PayloadProps | undefined> {
): State<PayloadProps | undefined> & { refetch: () => Promise<void> } {
const [state, setStates] = useState<State<PayloadProps | undefined>>({
loading: true,
success: null,
Expand All @@ -19,37 +19,28 @@ function useFetch<PayloadProps, FunctionParams>(
payload: undefined,
});

const loadingRef = useRef(0);

useEffect(() => {
const fetchData = useCallback(async (): Promise<void> => {
setStates((prev) => ({ ...prev, loading: true }));
try {
(async (): Promise<void> => {
if (state.loading) {
const response = await functions(param);

if (loadingRef.current === 0) {
loadingRef.current = 1;
const response = await functions(param);

if (response.statusCode === 200) {
setStates({
loading: false,
error: false,
success: true,
payload: response.payload,
errorMessage: '',
});
} else {
setStates({
loading: false,
error: true,
success: false,
payload: undefined,
errorMessage: response.error as string,
});
}
}
}
})();
if (response.statusCode === 200) {
setStates({
loading: false,
error: false,
success: true,
payload: response.payload,
errorMessage: '',
});
} else {
setStates({
loading: false,
error: true,
success: false,
payload: undefined,
errorMessage: response.error as string,
});
}
} catch (error) {
setStates({
payload: undefined,
Expand All @@ -59,13 +50,16 @@ function useFetch<PayloadProps, FunctionParams>(
errorMessage: error as string,
});
}
return (): void => {
loadingRef.current = 1;
};
}, [functions, param, state.loading]);
}, [functions, param]);

// Initial fetch
useEffect(() => {
fetchData();
}, [fetchData]);

return {
...state,
refetch: fetchData,
};
}

Expand Down
Loading