-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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(web): select primary integration modal #3869
Conversation
import { successMessage } from '../../utils/notifications'; | ||
import { QueryKeys } from '../query.keys'; | ||
|
||
export const useMakePrimaryIntegration = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the hook that is used when the user clicks the "Make primary" button in the modal...
@@ -21,6 +21,7 @@ interface IButtonProps extends ButtonProps { | |||
pulse?: boolean; | |||
sx?: Sx; | |||
iconPosition?: 'left' | 'right'; | |||
testId?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed the warning in the console
border: 1px solid ${colors.gradientMiddle}; | ||
`; | ||
|
||
export const Star = (props: React.ComponentPropsWithoutRef<'svg'>) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The star icon, I don't know why but when I export the whole "Primary Provider Icon" from Figma - the icon doesn't have a correct viewport, that's why I just exported the star and made the wrapper circle by hands... I will ask Nikolay to take a look
paddingLeft: withSelection ? 10 : 30, | ||
paddingRight: withSelection ? 10 : 30, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the styles for the table when it has an option to select a row... more about it below
withSelection?: boolean; | ||
withRowClickSelection?: boolean; | ||
initialSelectedRows?: Record<IdType<T>, boolean>; | ||
onRowClick?: (row: Row<T>) => void; | ||
onRowSelect?: (row: Row<T>) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the functionality to the table that will allow us to have a radio button in the first column of the table. Only one row could be selected and a callback triggered on selection.
const initialSelectedIndex = useMemo(() => { | ||
const primary = findPrimaryIntegration(integrationsByEnvAndChannel ?? []); | ||
|
||
return integrationsByEnvAndChannel?.indexOf(primary); | ||
}, [integrationsByEnvAndChannel]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
find the initial index for a primary integration which will be selected in the modal
const onRowSelectCallback = (row: Row<ITableIntegration>) => { | ||
setSelectedState({ | ||
selectedIntegrationId: row.original.integrationId, | ||
isActive: row.original.active, | ||
selectedRowId: row.id, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the integration is selected by clicking on radio button we do store the info in the state
<When truthy={isLoading}> | ||
<Table loading loadingItems={5} data-test-id="integrations-list-table" columns={columns} withSelection /> | ||
</When> | ||
<When truthy={!isLoading}> | ||
<Table | ||
data-test-id="integrations-list-table" | ||
columns={columns} | ||
data={integrationsByEnvAndChannel} | ||
withSelection | ||
onRowSelect={onRowSelectCallback} | ||
initialSelectedRows={initialSelectedIndex > -1 ? { [initialSelectedIndex]: true } : undefined} | ||
/> | ||
</When> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the trick to have a initially selected row... in the beginning, we don't have data for integrations, so we cant calculate the selected row index... if we would set it to -1 in the begging then after the data arrives that index value would be different ex. 1, but the state won't be updated as it's just used for the init...
that's why we do show one table on loading and another when the data is ready...
useEffectOnce(() => { | ||
openSelectPrimaryIntegrationModal({ | ||
environmentId: selectedProvider?.environmentId, | ||
channelType: selectedProvider?.channel, | ||
}); | ||
}, hasToSelectPrimaryProvider && !!selectedProvider); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
show the modal when we have a hasToSelectPrimaryProvider
flag set and the integration is loaded
@@ -32,6 +18,8 @@ export const mapToTableIntegration = ( | |||
|
|||
return { | |||
name: integration.name ?? provider?.displayName, | |||
isPrimary: integration.isPrimary, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this flag should be returned from the BE to mark the integration as primary
export const StarEmpty = (props: React.ComponentPropsWithoutRef<'svg'>) => { | ||
return ( | ||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 13" width={18} height={18} {...props}> | ||
<path | ||
fill="#fff" | ||
d="M4.9 9.883 7 8.617 9.1 9.9l-.55-2.4 1.85-1.6-2.433-.217-.966-2.266-.967 2.25-2.433.216L5.45 7.5 4.9 9.883Zm-2.016 2.784 1.083-4.684-3.633-3.15 4.8-.416L7.001 0l1.866 4.417 4.8.416-3.633 3.15 1.083 4.684-4.116-2.484-4.117 2.484Z" | ||
/> | ||
</svg> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another different star shown in the mark as primary action
<Popover | ||
opened={isPopoverOpened && original.primary} | ||
withArrow | ||
withinPortal | ||
offset={10} | ||
transitionDuration={300} | ||
position="top" | ||
width={230} | ||
styles={{ dropdown: { minHeight: 'initial !important' } }} | ||
content={ | ||
<Description> | ||
{`The primary provider instance within the ${original.channel.toLowerCase()} ` + | ||
`channel in the ${original.environment.toLowerCase()} environment.`} | ||
</Description> | ||
} | ||
target={ | ||
<IconHolder onMouseEnter={() => setPopoverOpened(true)} onMouseLeave={() => setPopoverOpened(false)}> | ||
<Image src={original.logoFileName[`${colorScheme}`]} alt={original.name} /> | ||
{original.primary && <Star />} | ||
</IconHolder> | ||
} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the popover shown over the integration icon when it's primary
const onDeleteClick = () => { | ||
if (!provider) { | ||
return; | ||
} | ||
|
||
if (provider.primary) { | ||
openModal({ | ||
environmentId: provider.environmentId, | ||
channelType: provider.channel, | ||
onClose: () => { | ||
deleteIntegration({ | ||
id: provider.integrationId, | ||
name: provider.name || provider.displayName, | ||
}); | ||
}, | ||
}); | ||
|
||
return; | ||
} | ||
|
||
deleteIntegration({ | ||
id: provider.integrationId, | ||
name: provider.name || provider.displayName, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the delete integration dropdown action is clicked we do check if the integration was primary, and if yes then the user has to pick another primary integration
{canMarkAsPrimary && ( | ||
<Dropdown.Item | ||
onClick={() => { | ||
makePrimaryIntegration({ id: provider.integrationId }); | ||
}} | ||
icon={<StarEmpty />} | ||
disabled={isLoading || isMarkingPrimary} | ||
> | ||
Mark as primary | ||
</Dropdown.Item> | ||
)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only email/sms integrations could be marked as primary and also the integration that is not already primary ;)
const initialSelectedIndex = useMemo(() => { | ||
const primary = integrationsByEnvAndChannel.find((el) => el.primary); | ||
if (primary) { | ||
return integrationsByEnvAndChannel.indexOf(primary ?? {}); | ||
} | ||
|
||
return -1; | ||
}, [integrationsByEnvAndChannel]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calculate the index of default selected integration, it will be the one that is a primary
const updateAndSelectPrimaryIntegration = async (data: IConstructIntegrationDto) => { | ||
if (!selectedProvider) { | ||
return; | ||
} | ||
|
||
const { channel: selectedChannel, environmentId, primary } = selectedProvider; | ||
const isActiveFieldChanged = dirtyFields.active; | ||
const hasSameChannelActiveIntegration = !!providers | ||
.filter((el) => !NOVU_PROVIDERS.includes(el.providerId)) | ||
.find((el) => el.active && el.channel === selectedChannel && el.environmentId === environmentId); | ||
const isChannelSupportPrimary = CHANNELS_WITH_PRIMARY.includes(selectedChannel); | ||
|
||
if ( | ||
isActiveFieldChanged && | ||
isChannelSupportPrimary && | ||
((isActive && hasSameChannelActiveIntegration) || (!isActive && primary)) | ||
) { | ||
openSelectPrimaryIntegrationModal({ | ||
environmentId: selectedProvider?.environmentId, | ||
channelType: selectedProvider?.channel, | ||
onClose: () => { | ||
updateIntegration(data); | ||
}, | ||
}); | ||
|
||
return; | ||
} | ||
|
||
updateIntegration(data); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the update integration button is clicked we do check if:
- integration active field has changed and the integration channel supports primary (email/sms)
- and is active and there are other active for the same channel
- or is not active and is a primary
then we do show the modal to pick a new primary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
small question in this cycle we did not want to touch the workflow providers tab? asking regarding the star in the provider list there.
Do we need to present the deleted provider in the modal?
https://www.loom.com/share/f68a5a64702e4232973d85b02056bcd5
i am not sure if this state is possible but, another thing i encountered in the empty state where i could not add new provider, i was either redirected to workflow page or i was missing the create sidebar.
could be something in my machine tho, the flickering, in the end, is page refresh
https://www.loom.com/share/36a0bf4fea8640f782fec1770fde08f5
Another possible not possible state is the following
https://www.loom.com/share/68da5da165ce4bcc9721927ce5d30904
if it is possible should show the modal at all?
the thing i did not had the time to check is analytics and backward compatibility.
other than that your code looks as clean as always ✨
no in the next cycle, we won't work on the provider's tab
yes, totally agree with you, I'll exclude it from the modal
I will investigate this case, it's weird.
yes, I will fix this one too
We totally missed the analytics for the multi-channel provider's feature and for the new integrations list page, thank you for noticing that, I'll add it to our backlog.
thank you @djabarovgeorge for these findings and all your time spend on this feature 🙂 you really helped me a lot! 🤝 |
@djabarovgeorge all the things you've found have been fixed :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀
the last thing that is left for us is the integrations related tests that are failing.
What change does this PR introduce?
Implemented the Select Primary Integration modal that will be shown when after the user creates the integration if there are already any integrations for the same env and channel.
Why was this change needed?
Multi-Channel Provider PRD
Other information (Screenshots)