Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
bimalgrg519 committed Jan 23, 2025
1 parent 341d99e commit 4bc06a7
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 63 deletions.
3 changes: 2 additions & 1 deletion ui/web-v2/src/assets/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@
"notification.sort.nameZa": "Name Z-A",
"notification.sort.newest": "Newest",
"notification.sort.oldest": "Oldest",
"notification.tagsTooltipMessage": "The configured tags in the Feature Flag when was created. It will only notify you if the tags match the tag configured in the Feature Flag. When empty, it will notify you of any changes to all the flags.",
"notification.update.header.description": "A notification lets you know when someone adds or updates something on the admin console and operational tasks status.",
"notification.update.header.title": "Update the notification",
"progressiveRolloutWarningMessages.alreadyProgressiveRollout": "Cannot execute more than one progressive rollout at the same time.",
Expand Down Expand Up @@ -633,4 +634,4 @@
"urlCode": "URL code",
"warning": "Warning",
"yes": "Yes"
}
}
1 change: 1 addition & 0 deletions ui/web-v2/src/assets/lang/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@
"notification.sort.nameZa": "名前 Z-A",
"notification.sort.newest": "新しい順",
"notification.sort.oldest": "古い順",
"notification.tagsTooltipMessage": "タグはフラグの作成時に設定されたタグです。タグがフラグで設定されたタグと一致する場合にのみ通知されます。空の場合は、全てのフラグが更新時に通知されます。",
"notification.update.header.description": "通知機能を使用することによって、管理コンソールでの追加・更新をした時や、運用タスクの状況を確認することができます。",
"notification.update.header.title": "通知の更新",
"progressiveRolloutWarningMessages.alreadyProgressiveRollout": "同時に複数のプログレッシブロールアウトを実行することはできません。",
Expand Down
168 changes: 144 additions & 24 deletions ui/web-v2/src/components/CheckBoxList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import { FC, memo, useState } from 'react';
import { FC, memo, useEffect, useState } from 'react';

import { CheckBox } from '../CheckBox';
import { useIntl } from 'react-intl';
import { messages } from '../../lang/messages';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { AppState } from '../../modules';
import { Tag } from '../../proto/tag/tag_pb';
import { listTags, selectAll as selectAllTags } from '../../modules/tags';
import { Subscription } from '../../proto/notification/subscription_pb';
import { Select } from '../Select';
import { Controller, useFormContext } from 'react-hook-form';
import { AppDispatch } from '../../store';
import { ListTagsRequest } from '../../proto/tag/service_pb';
import { useCurrentEnvironment } from '../../modules/me';
import { HoverPopover } from '../HoverPopover';
import { classNames } from '../../utils/css';
import { InformationCircleIcon } from '@heroicons/react/outline';

export interface Option {
value: string;
Expand All @@ -17,6 +32,25 @@ export interface CheckBoxListProps {

export const CheckBoxList: FC<CheckBoxListProps> = memo(
({ onChange, options, defaultValues, disabled }) => {
const { formatMessage: f } = useIntl();
const methods = useFormContext();
const {
control,
formState: { errors }
} = methods;

const dispatch = useDispatch<AppDispatch>();
const currentEnvironment = useCurrentEnvironment();

const tagsList = useSelector<AppState, Tag.AsObject[]>(
(state) => selectAllTags(state.tags),
shallowEqual
);

const featureFlagTagsList = tagsList.filter(
(tag) => tag.entityType === Tag.EntityType.FEATURE_FLAG
);

const [checkedItems] = useState(() => {
const items = new Map();
defaultValues &&
Expand All @@ -26,6 +60,19 @@ export const CheckBoxList: FC<CheckBoxListProps> = memo(
return items;
});

useEffect(() => {
dispatch(
listTags({
environmentId: currentEnvironment.id,
pageSize: 0,
cursor: '',
orderBy: ListTagsRequest.OrderBy.DEFAULT,
orderDirection: ListTagsRequest.OrderDirection.ASC,
searchKeyword: null
})
);
}, [dispatch]);

const handleOnChange = (value: string, checked: boolean) => {
if (checked) {
checkedItems.set(value, value);
Expand All @@ -45,31 +92,104 @@ export const CheckBoxList: FC<CheckBoxListProps> = memo(
<div className="divide-y divide-gray-300">
{options.map((item, index) => {
return (
<div
key={item.label}
className="relative flex items-start py-4"
>
<div className="min-w-0 flex-1 text-sm">
<label htmlFor={`id_${index}`} key={`key_${index}`}>
<p className="text-sm font-medium text-gray-700">
{item.label}
</p>
{item.description && (
<p className="text-sm text-gray-500">
{item.description}
<div key={item.label}>
<div className="relative flex items-start py-4">
<div className="min-w-0 flex-1 text-sm">
<label htmlFor={`id_${index}`} key={`key_${index}`}>
<p className="text-sm font-medium text-gray-700">
{item.label}
</p>
)}
</label>
</div>
<div className="ml-3 flex items-center h-5">
<CheckBox
id={`id_${index}`}
value={item.value}
onChange={handleOnChange}
defaultChecked={checkedItems.has(item.value)}
disabled={disabled}
/>
{item.description && (
<p className="text-sm text-gray-500">
{item.description}
</p>
)}
</label>
</div>
<div className="ml-3 flex items-center h-5">
<CheckBox
id={`id_${index}`}
value={item.value}
onChange={handleOnChange}
defaultChecked={checkedItems.has(item.value)}
disabled={disabled}
/>
</div>
</div>
{item.value ===
Subscription.SourceType.DOMAIN_EVENT_FEATURE.toString() &&
checkedItems.has(
Subscription.SourceType.DOMAIN_EVENT_FEATURE.toString()
) && (
<div className="mb-4">
<div className="flex space-x-1 items-center">
<label htmlFor="tags">
<span className="input-label">
{f(messages.tags.title)}
</span>
</label>
<HoverPopover
render={() => {
return (
<div
className={classNames(
'border shadow-sm bg-white text-gray-500 p-1',
'text-xs rounded whitespace-normal break-words w-64'
)}
>
{f(messages.notification.tagsTooltipMessage)}
</div>
);
}}
>
<div
className={classNames(
'hover:text-gray-500 mb-[2px]'
)}
>
<InformationCircleIcon
className="w-5 h-5 text-gray-400"
aria-hidden="true"
/>
</div>
</HoverPopover>
</div>
<Controller
name="featureFlagTagsList"
control={control}
render={({ field }) => {
return (
<Select
isMulti
value={field.value?.map((tag: string) => {
return {
value: tag,
label: tag
};
})}
options={featureFlagTagsList.map((tag) => ({
label: tag.name,
value: tag.name
}))}
onChange={(options: Option[]) => {
field.onChange(options.map((o) => o.value));
}}
closeMenuOnSelect={false}
placeholder={f(messages.tags.tagsPlaceholder)}
disabled={disabled}
/>
);
}}
/>
{errors.tags && (
<p className="input-error">
{errors.tags && (
<span role="alert">{errors.tags.message}</span>
)}
</p>
)}
</div>
)}
</div>
);
})}
Expand Down
4 changes: 4 additions & 0 deletions ui/web-v2/src/components/NotificationList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const NotificationList: FC<NotificationListProps> = memo(
shallowEqual
);

console.log({
notificationList
});

return (
<div className="w-full">
<div className="flex items-stretch mb-8 text-sm">
Expand Down
5 changes: 5 additions & 0 deletions ui/web-v2/src/lang/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2389,6 +2389,11 @@ export const messages = {
})
}
},
tagsTooltipMessage: defineMessage({
id: 'notification.tagsTooltipMessage',
defaultMessage:
'The configured tags in the Feature Flag when was created. It will only notify you if the tags match the tag configured in the Feature Flag. When empty, it will notify you of any changes to all the flags.'
}),
slackIncomingWebhookUrl: defineMessage({
id: 'notification.slackIncomingWebhookUrl',
defaultMessage: 'Slack incoming webhook URL'
Expand Down
55 changes: 37 additions & 18 deletions ui/web-v2/src/modules/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
DisableSubscriptionCommand,
AddSourceTypesCommand,
DeleteSourceTypesCommand,
RenameSubscriptionCommand
RenameSubscriptionCommand,
UpdateSubscriptionFeatureFlagTagsCommand
} from '../proto/notification/command_pb';
import {
Recipient,
Expand Down Expand Up @@ -88,6 +89,7 @@ export interface CreateNotificationParams {
Subscription.SourceTypeMap[keyof Subscription.SourceTypeMap]
>;
webhookUrl: string;
featureFlagTagsList: string[];
}

export const createNotification = createAsyncThunk<
Expand All @@ -98,6 +100,9 @@ export const createNotification = createAsyncThunk<
const cmd = new CreateSubscriptionCommand();
cmd.setName(params.name);
cmd.setSourceTypesList(params.sourceTypes);
params.featureFlagTagsList &&
params.featureFlagTagsList.length > 0 &&
cmd.setFeatureFlagTagsList(params.featureFlagTagsList);

const recipient = new Recipient();
recipient.setType(Recipient.Type.SLACKCHANNEL);
Expand All @@ -115,6 +120,7 @@ export const createNotification = createAsyncThunk<
const request = new CreateSubscriptionRequest();
request.setEnvironmentId(params.environmentId);
request.setCommand(cmd);

await subscriptionGrpc.createSubscription(request);
});

Expand All @@ -128,6 +134,7 @@ export interface UpdateNotificationParams {
sourceTypes: Array<
Subscription.SourceTypeMap[keyof Subscription.SourceTypeMap]
>;
featureFlagTagsList: string[];
}

export const updateNotification = createAsyncThunk<
Expand All @@ -145,23 +152,35 @@ export const updateNotification = createAsyncThunk<
request.setRenameSubscriptionCommand(cmd);
}

if (params.sourceTypes) {
const addList = params.sourceTypes.filter(
(type) => !params.currentSourceTypes.includes(type)
);
if (addList.length > 0) {
const cmd = new AddSourceTypesCommand();
cmd.setSourceTypesList(addList);
request.setAddSourceTypesCommand(cmd);
}
const deleteList = params.currentSourceTypes.filter(
(type) => !params.sourceTypes.includes(type)
);
if (deleteList.length > 0) {
const cmd = new DeleteSourceTypesCommand();
cmd.setSourceTypesList(deleteList);
request.setDeleteSourceTypesCommand(cmd);
}
const addList = params.sourceTypes.filter(
(type) => !params.currentSourceTypes.includes(type)
);
// DOMAIN_EVENT_FEATURE must be included in sourceTypes if featureFlagTagsList is set
// if (
// params.featureFlagTagsList &&
// !params.sourceTypes.includes(Subscription.SourceType.DOMAIN_EVENT_FEATURE)
// ) {
// addList.push(Subscription.SourceType.DOMAIN_EVENT_FEATURE);
// }
if (addList.length > 0) {
const cmd = new AddSourceTypesCommand();
cmd.setSourceTypesList(addList);
request.setAddSourceTypesCommand(cmd);
}
const deleteList = params.currentSourceTypes.filter(
(type) => !params.sourceTypes.includes(type)
);
if (deleteList.length > 0) {
const cmd = new DeleteSourceTypesCommand();
cmd.setSourceTypesList(deleteList);
request.setDeleteSourceTypesCommand(cmd);
}
console.log({ params, addList, deleteList });

if (params.featureFlagTagsList) {
const cmd = new UpdateSubscriptionFeatureFlagTagsCommand();
cmd.setFeatureFlagTagsList(params.featureFlagTagsList);
request.setUpdateSubscriptionFeatureTagsCommand(cmd);
}

await subscriptionGrpc.updateSubscription(request);
Expand Down
30 changes: 28 additions & 2 deletions ui/web-v2/src/pages/notification/formSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from '../../constants/notification';
import { intl } from '../../lang';
import { messages } from '../../lang/messages';
import { Subscription } from '../../proto/notification/subscription_pb';

yup.setLocale(yupLocale);

Expand All @@ -18,15 +19,40 @@ const sourceTypesSchema = yup
NOTIFICATION_SOURCE_TYPES_MIN_LENGTH,
intl.formatMessage(messages.input.error.minSelectOptionLength)
);

const webhookUrlSchema = yup.string().required().url();
const featureFlagTagsListSchema = yup
.array()
.of(yup.string())
.test(
'tags-required-for-flag',
intl.formatMessage(messages.input.error.required),
function (featureFlagTagsList) {
const { sourceTypes } = this.parent;
const hasFlag = sourceTypes?.includes(
Subscription.SourceType.DOMAIN_EVENT_FEATURE.toString()
);

// If 'Flag' is selected, ensure tags are present and not empty.
if (
hasFlag &&
(!featureFlagTagsList || featureFlagTagsList.length === 0)
) {
return false;
}
return true;
}
);

export const addFormSchema = yup.object().shape({
name: nameSchema,
sourceTypes: sourceTypesSchema,
webhookUrl: webhookUrlSchema
webhookUrl: webhookUrlSchema,
featureFlagTagsList: featureFlagTagsListSchema
});

export const updateFormSchema = yup.object().shape({
name: nameSchema,
sourceTypes: sourceTypesSchema
sourceTypes: sourceTypesSchema,
featureFlagTagsList: featureFlagTagsListSchema
});
Loading

0 comments on commit 4bc06a7

Please sign in to comment.