-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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(onboarding): add messaging integration onboarding to project creation #79194
base: master
Are you sure you want to change the base?
Conversation
Bundle ReportChanges will increase total bundle size by 17.37kB (0.06%) ⬆️. This is within the configured threshold ✅ Detailed changes
|
Codecov ReportAttention: Patch coverage is ✅ All tests successful. No failed tests found. Additional details and impacted files@@ Coverage Diff @@
## master #79194 +/- ##
===========================================
- Coverage 88.51% 78.33% -10.18%
===========================================
Files 3044 7126 +4082
Lines 189155 314565 +125410
Branches 31095 51375 +20280
===========================================
+ Hits 167425 246429 +79004
- Misses 15654 61686 +46032
- Partials 6076 6450 +374 |
const [alertNotificationAction, setAlertNotificationAction] = useState< | ||
IssueAlertActionType[] | ||
>([IssueAlertActionType.NOTIFY_EMAIL]); | ||
const [alertNotificationProvider, setAlertNotificationProvider] = useState< | ||
string | undefined | ||
>(undefined); | ||
const [alertNotificationIntegration, setAlertNotificationIntegration] = useState< | ||
OrganizationIntegration | undefined | ||
>(undefined); | ||
const [alertNotificationChannel, setAlertNotificationChannel] = useState< | ||
string | undefined | ||
>(undefined); |
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.
Does all this state belong in the createProject
component itself? Should this live somewhere more contained?
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.
I thought about this too, but I wasn't sure where it would go because the alert rule is created here after the project itself has been created.
I also considered turning provider, integration, and channel into an object, but decided against it since I would just have to create individual setters for readability anyways- lmk if that wasn't the right approach tho
const integrationAction = useMemo(() => { | ||
const id = alertNotificationAction.find( | ||
action => action !== IssueAlertActionType.NOTIFY_EMAIL | ||
); | ||
if (id === IssueAlertActionType.SLACK) { | ||
return [ | ||
{ | ||
id: id, | ||
workspace: alertNotificationIntegration?.id, | ||
channel: alertNotificationChannel, | ||
}, | ||
]; | ||
} | ||
if (id === IssueAlertActionType.DISCORD) { | ||
return [ | ||
{ | ||
id: id, | ||
server: alertNotificationIntegration?.id, | ||
channel_id: alertNotificationChannel, | ||
}, | ||
]; | ||
} | ||
if (id === IssueAlertActionType.MS_TEAMS) { | ||
return [ | ||
{ | ||
id: id, | ||
team: alertNotificationIntegration?.id, | ||
channel: alertNotificationChannel, | ||
}, | ||
]; | ||
} | ||
return undefined; | ||
}, [alertNotificationAction, alertNotificationIntegration, alertNotificationChannel]); |
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.
Is the useMemo necessary here to make sure the object identity is stable?
@@ -136,7 +185,28 @@ function CreateProject() { | |||
}, | |||
} | |||
); | |||
ruleId = ruleData.id; | |||
ruleIds?.push(ruleData.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.
Why do you need the ?.
const { | ||
alertNotificationAction: action, | ||
alertNotificationProvider: provider, | ||
setAlertNotificationAction: setAction, | ||
setAlertNotificationIntegration: setIntegration, | ||
setAlertNotificationProvider: setProvider, | ||
} = useIssueAlertNotificationContext(); |
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.
Looks like this context is only used once here. Do we really need a context?
Typically you want a context when you need state shared a cross indeterminately nested components where you wouldn't want to have to prop drill constantly
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.
I wanted to avoid prop drilling from createProject
, the context is being used in both issueAlertNotificationOptions
and the nested messagingIntegrationAlertRule
component. I think the context is also useful to avoid having to pass all of the states/setters through issueAlertOptions
, which doesn't use them.
return selectedValues.some(v => v !== MultipleCheckboxOptions.EMAIL); | ||
}, [selectedValues]); | ||
|
||
const onChange = values => { |
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.
values is missing a type
const onChange = values => { | |
const onChange = values => { |
</MultipleCheckbox> | ||
{shouldRenderSetupButton && ( | ||
<SetupMessagingIntegrationButton | ||
refetchConfigs={refetchConfigs} |
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.
could pass refetch directly since the refetchConfigs function doesn't add anything
refetchConfigs={refetchConfigs} | |
refetchConfigs={messagingIntegrationsQuery.refetch} |
))} | ||
{Object.keys(errors).map(key => { | ||
const label = | ||
key === 'actions' ? 'Notify via integration' : startCase(key); |
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.
key === 'actions' ? 'Notify via integration' : startCase(key); | |
key === 'actions' ? t('Notify via integration') : startCase(key); |
Also you probably don't want to just take the key and capitalize the first letter, instead what you probably want is a mapping of keys to translated strings
const errors: Record<typeof key, string> = {
key1: t('Key1'),
key2: t('Key2'),
...
}
The Record<typeof key, string>
is just an example, if the keys aren't actually know at type check time it could just be Record<string, string>
Something important to remmeber is that you can't give t()
dynamic values like t(startCase(key))
. This is because the way translations are extracted and sent to our translators is that we statically analyze our code and extract out the strings inside of the t("string")
calls. So if you did t(dynamicVar)
the extractor doesn't know what the string is and can't include it in the list of strings to translate.
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.
I'm assuming they used startCase
to be able to generically handle any errors that could occur- I'm not familiar enough with all of the possible errors to be able to make a record here 😕 I just wanted to handle this special "actions" case because this label is not clear where the error occurred (my guess is that this error was originally intended to only be shown on the Alert Creation page, where it's more clear)
}, | ||
}; | ||
|
||
enum MultipleCheckboxOptions { |
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.
can we use a const enum here if we're not using the index
enum MultipleCheckboxOptions { | |
const enum MultipleCheckboxOptions { |
alertNotificationAction: IssueAlertActionType[]; | ||
alertNotificationChannel: string | undefined; | ||
alertNotificationIntegration: OrganizationIntegration | undefined; | ||
alertNotificationProvider: string | undefined; | ||
setAlertNotificationAction: (method: IssueAlertActionType[]) => void; | ||
setAlertNotificationChannel: (channel: string | undefined) => void; | ||
setAlertNotificationIntegration: ( | ||
integration: OrganizationIntegration | undefined | ||
) => void; | ||
setAlertNotificationProvider: (provider: string | undefined) => 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.
I think we can probably get rid of this context, but just for the future, I don't think these keys need to be nearly this verbose. They are already contained inside a context called IssueAlertNotificationContext
so it would be easier to read these by just stripping off the alertNotification
part.
const providersToIntegrations = useMemo(() => { | ||
const map: {[key: string]: OrganizationIntegration[]} = {}; | ||
if (!messagingIntegrationsQuery.data) { | ||
return {}; |
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.
returning {}
loses the type
return {}; | |
return map; |
edbf08e
to
43e4ea7
Compare
if ( | ||
organization.features.includes( | ||
'messaging-integration-onboarding-project-creation' | ||
) && | ||
shouldCreateRule | ||
) { |
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.
Can this check with the flag be part of the createNotificationAction
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.
You can just pass shouldCreateRule
to createNotificationAction
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.
in which case the function just becomes a no-op
shouldCreateRule | ||
) { | ||
const ruleData = await createNotificationAction({ | ||
api, |
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.
I don't think you'll need to pass api
here, just call useApi
inside the hook
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 I tried to call useApi
here I ran into an error about only calling hooks from the top-level component, unless I'm doing something wrong?
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.
you want useApi
inside the hook where you define createNotificationAction
actions: alertNotificationActions, | ||
provider, | ||
integration, | ||
channel, | ||
setActions: setAlertNotificationActions, | ||
setProvider, | ||
setIntegration, | ||
setChannel, |
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.
Do you actually need to destructure all these? Are you able to just have it look like this
actions: alertNotificationActions, | |
provider, | |
integration, | |
channel, | |
setActions: setAlertNotificationActions, | |
setProvider, | |
setIntegration, | |
setChannel, | |
...notificationProps, |
notificationProps={{ | ||
actions: alertNotificationActions, | ||
channel, | ||
integration, | ||
provider, | ||
setActions: setAlertNotificationActions, | ||
setChannel, | ||
setIntegration, | ||
setProvider, | ||
}} |
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.
If you make my change suggested here https://github.com/getsentry/sentry/pull/79194/files#r1805048688 could this just become
notificationProps={{ | |
actions: alertNotificationActions, | |
channel, | |
integration, | |
provider, | |
setActions: setAlertNotificationActions, | |
setChannel, | |
setIntegration, | |
setProvider, | |
}} | |
notificationProps={notificationProps} |
@@ -406,6 +427,13 @@ describe('CreateProject', function () { | |||
await userEvent.clear(screen.getByTestId('range-input')); | |||
expect(getSubmitButton()).toBeDisabled(); | |||
|
|||
await userEvent.click( | |||
screen.getByRole('checkbox', { | |||
name: /notify via integration \(slack, discord, ms teams, etc\.\)/i, |
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.
do we actually need the regex here?
const label = | ||
key === 'actions' ? t('Notify via integration') : startCase(key); |
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.
Let's definitely improve this and create a key → error text mapping if we can
// it('disables integration select when there is only one option', function () { | ||
// render( | ||
// getComponent({ | ||
// alertNotificationIntegration: discordIntegrations[0], | ||
// alertNotificationProvider: 'discord', | ||
// }) | ||
// ); | ||
// screen.getByRole('text').click(); | ||
// expect(screen.getByRole('textbox')).toBeDisabled(); | ||
// }); |
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.
comment
} | ||
|
||
return ( | ||
<div> |
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.
Extra div
necissary?
<Rule> | ||
{t('Send')} | ||
<InlineSelectControl | ||
label="provider" |
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.
Label should be translated. Does this actually set the aria-label
though? Do you need to use aria-label
?
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.
Yes I still need to fix this, this was probably why the test I commented out wasn't working 😵💫
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.
Unfortunately the types on these controls are pretty broken 😥
<Rule> | ||
{t('Send')} | ||
<InlineSelectControl | ||
label="provider" | ||
disabled={Object.keys(providersToIntegrations).length === 1} | ||
value={provider} | ||
options={providerOptions} | ||
onChange={p => { | ||
setProvider(p.value); | ||
setIntegration(providersToIntegrations[p.value][0]); | ||
setChannel(''); | ||
}} | ||
/> | ||
{t('notification to the')} | ||
<InlineSelectControl | ||
label="integration" | ||
role="select" | ||
disabled={integrationOptions.length === 1} | ||
value={integration} | ||
options={integrationOptions} | ||
onChange={i => setIntegration(i.value)} | ||
/> | ||
{providerDetails[provider]?.label} | ||
<InlineInput | ||
// label="channel" | ||
type="text" | ||
value={channel || ''} | ||
placeholder={providerDetails[provider]?.placeholder} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | ||
setChannel(e.target.value) | ||
} | ||
/> | ||
</Rule> |
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 whole thing actually needs to be using tct
(translate string with component interpolation).
The reason for this is because the english sentence structure is not the same as other language sentence structures.
Right now this might produce something that looks like
Send [Slack] notification to the [my slack] workspace to [#alerts]
But now if we take that sentence and look at what it looks like in Japanese you get
[Slack]通知を[my slack]ワークスペースに[#alerts]に送信します
Right away you can notice that the ordering is a little bit different.
In Japanese the sentence uses Subject Object Verb ordering, unlike english that uses Subject Verb Object.
In Japanese "[Slack]通知" is "Slack Notification", then at the end we have "送信します" which roughly means "send / transmit".
However the way this is written right now, the sentence structure of this sentence and these components cannot be moved around like that. In fact, it's even worse because the translator is only given two strings to translate
Send
and notification to the
. Out of context in other languages it's impossible to know what additional gramatical markers should be include.
Now, if you were to change this to use tct
we can represent the whole sentence.
It's a little tricky for this example, because providerDetails[provider]?.label
is actually also part of the sentence and should be translated. So what we probably want is that instead of having the provider configuration look like this:
slack: {
name: 'Slack',
action: IssueAlertActionType.SLACK,
label: 'workspace to',
placeholder: 'channel, e.g. #critical',
},
discord: {
name: 'Discord',
action: IssueAlertActionType.DISCORD,
label: 'server in the channel',
placeholder: 'channel ID or URL',
},
It should look closer to
slack: {
name: t('Slack'),
action: IssueAlertActionType.SLACK,
placeholder: t('channel, e.g. #critical'),
makeSentence: ({providerName, integrationName, target}) => tct(
'Send [providerName] notification to the [integrationName] workspace to [target]',
{
providerName,
integrationName,
target
}
)
},
discord: {
name: t('Discord'),
action: IssueAlertActionType.DISCORD,
placeholder: t('channel ID or URL'),
makeSentence: ({providerName, integrationName, target}) => tct(
'Send [providerName] notification to the [integrationName] server in the channel [target]',
{
providerName,
integrationName,
target
}
)
},
now the translator will have the string
Send [providerName] notification to the [integrationName] workspace to [target]
If the translator translates this to Japanese it will look like this:
[providerName]通知を[integrationName]ワークスペース、[target]に送信します
And for discord
Send [providerName] notification to the [integrationName] server in the channel [target]
[providerName]通知をチャネル[target]の[integrationName]サーバーに送信します
(These translations probably aren't perfect @corps can check me)
See how they were able to move around the object and the verb. That wasn't possible before.
Now this code looks like
<Rule> | |
{t('Send')} | |
<InlineSelectControl | |
label="provider" | |
disabled={Object.keys(providersToIntegrations).length === 1} | |
value={provider} | |
options={providerOptions} | |
onChange={p => { | |
setProvider(p.value); | |
setIntegration(providersToIntegrations[p.value][0]); | |
setChannel(''); | |
}} | |
/> | |
{t('notification to the')} | |
<InlineSelectControl | |
label="integration" | |
role="select" | |
disabled={integrationOptions.length === 1} | |
value={integration} | |
options={integrationOptions} | |
onChange={i => setIntegration(i.value)} | |
/> | |
{providerDetails[provider]?.label} | |
<InlineInput | |
// label="channel" | |
type="text" | |
value={channel || ''} | |
placeholder={providerDetails[provider]?.placeholder} | |
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | |
setChannel(e.target.value) | |
} | |
/> | |
</Rule> | |
<Rule> | |
{providerDetails[provider]?.makeSentence({ | |
providerName: ( | |
<InlineSelectControl | |
label="provider" | |
disabled={Object.keys(providersToIntegrations).length === 1} | |
value={provider} | |
options={providerOptions} | |
onChange={p => { | |
setProvider(p.value); | |
setIntegration(providersToIntegrations[p.value][0]); | |
setChannel(''); | |
}} | |
/> | |
), | |
integrationName: ( | |
<InlineSelectControl | |
label="integration" | |
role="select" | |
disabled={integrationOptions.length === 1} | |
value={integration} | |
options={integrationOptions} | |
onChange={i => setIntegration(i.value)} | |
/> | |
), | |
target: ( | |
<InlineInput | |
// label="channel" | |
type="text" | |
value={channel || ''} | |
placeholder={providerDetails[provider]?.placeholder} | |
onChange={(e: React.ChangeEvent<HTMLInputElement>) => | |
setChannel(e.target.value) | |
} | |
/> | |
), | |
}) ?? tct('Select via [providerName]', {providerName: (...)})} | |
</Rule> |
This can now be correctly translated across languages
const keyToErrorText = { | ||
actions: t('Notify via integration'), | ||
}; | ||
|
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.
I haven't been able to cause any other error messages, so right now the key --> error text mapping is very sparse 😔
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.
Can we trace back what they could be?
const ruleData = await createNotificationAction({ | ||
shouldCreateRule, | ||
name, | ||
projectSlug: projectData.slug, | ||
conditions, | ||
actionMatch, | ||
frequency, | ||
}); | ||
if (ruleData) { | ||
ruleIds.push(ruleData.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.
Nice!! Much cleaner!
@@ -407,7 +449,10 @@ function CreateProject() { | |||
<Alert type="error"> | |||
{Object.keys(errors).map(key => ( | |||
<div key={key}> | |||
<strong>{startCase(key)}</strong>: {errors[key]} | |||
<strong> | |||
{keyToErrorText[key] ? keyToErrorText[key] : startCase(key)} |
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.
{keyToErrorText[key] ? keyToErrorText[key] : startCase(key)} | |
{keyToErrorText[key] ?? startCase(key)} |
> | ||
<Wrapper> | ||
<MultipleCheckbox.Item value={MultipleCheckboxOptions.EMAIL} disabled> | ||
Notify via email |
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.
Notify via email | |
{t('Notify via email')} |
{!shouldRenderSetupButton && provider && ( | ||
<div> | ||
<MultipleCheckbox.Item value={MultipleCheckboxOptions.INTEGRATION}> | ||
Notify via integration (Slack, Discord, MS Teams, etc.) |
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.
Notify via integration (Slack, Discord, MS Teams, etc.) | |
{t('Notify via integration (Slack, Discord, MS Teams, etc.)')} |
<MessagingIntegrationAlertRule | ||
notificationProps={notificationProps} | ||
providersToIntegrations={providersToIntegrations} | ||
/> |
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.
Should this be inside of the MultipleCheckbox
container?
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.
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 probably fine, I was expecting that multiple checkbox was actually rendering a list, but it looks like it's actually just divs.
If it WAS rendering a list and this looked like
<ul>
<li>
<input type="checkbox" /> Label 1
</li>
<li>
<input type="checkbox" /> Notify via Integration...
</li>
<div>
Send ....
</div>
</ul>
This would actually be incorrect html. See
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul#technical_summary
Permitted content:
Zero or more <li>, <script> and <template> elements.
You're not allowed to have div
elements inside a ul
.
That said browsers render it fine. It's just not "proper" semantic HTML.
This can have downstream effects on things like screen readers for blind users. But that 's already a problem here since we're using unsemantic HTML to render this list of check boxes (just divs) instead of using a real list.
discord: { | ||
name: 'Discord', | ||
action: IssueAlertActionType.DISCORD, | ||
placeholder: 'channel ID or URL', |
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.
placeholder: 'channel ID or URL', | |
placeholder: t('channel ID or URL'), |
forgot these
), | ||
}, | ||
discord: { | ||
name: 'Discord', |
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.
name: 'Discord', | |
name: t('Discord'), |
msteams: { | ||
name: 'MSTeams', | ||
action: IssueAlertActionType.MS_TEAMS, | ||
placeholder: 'channel 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.
placeholder: 'channel ID', | |
placeholder: t('channel ID'), |
), | ||
}, | ||
msteams: { | ||
name: 'MSTeams', |
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.
Is it called MSTeams
or just Teams
? Was pretty sure this product is just called Teams
We really would want a translator note here, but as mentioned before, we don't support that so doing this might end up being confusing for our translators :(
name: 'MSTeams', | |
name: t('Teams'), |
maybe better is actually
name: 'MSTeams', | |
name: t('MS Teams'), |
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.
I believe it's called MS Teams
, or at least that's what we have everywhere else in our codebase
return undefined; | ||
} | ||
|
||
let integrationAction; |
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 missing a type
export default function IssueAlertNotificationOptions( | ||
notificationProps: IssueAlertNotificationProps | ||
) { | ||
const organization = useOrganization(); | ||
const {actions, provider, setActions, setIntegration, setProvider} = notificationProps; |
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.
export default function IssueAlertNotificationOptions( | |
notificationProps: IssueAlertNotificationProps | |
) { | |
const organization = useOrganization(); | |
const {actions, provider, setActions, setIntegration, setProvider} = notificationProps; | |
export default function IssueAlertNotificationOptions( | |
{actions, provider, setActions, setIntegration, setProvider}: IssueAlertNotificationProps | |
) { | |
const organization = useOrganization(); |
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.
Ah I wrote it this way bc notificationProps
still needs to be passed to MessagingIntegrationAlertRule
here
); | ||
|
||
const providersToIntegrations = useMemo(() => { | ||
const map: {[key: string]: OrganizationIntegration[]} = {}; |
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.
const map: {[key: string]: OrganizationIntegration[]} = {}; | |
const map: Record<string, OrganizationIntegration[]> = {}; |
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.
There's no real difference here, but one is easier to read
setProvider(firstProvider); | ||
|
||
const firstIntegration = providersToIntegrations[firstProvider][0]; | ||
setIntegration(firstIntegration); |
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.
Do we actually need this state if we can just derive all of this?
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.
or is this just for setting up defaults?
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.
If it's for setting up defaults I think you could probably do this in the constructor functions of the set states in the hook. Assuming we move the api calls into that hook
const organization = useOrganization(); | ||
const {actions, provider, setActions, setIntegration, setProvider} = notificationProps; | ||
|
||
const messagingIntegrationsQuery = useApiQuery<OrganizationIntegration[]>( |
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.
Any reason this stuff shouldn't be part of the useCreateNotificationAction
hook?
const shouldRenderSetupButton = useMemo(() => { | ||
return messagingIntegrationsQuery.data?.every(i => i.status !== 'active'); | ||
}, [messagingIntegrationsQuery.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.
I don't think we need a useMemo
here. This computation is not expensive and can just be run on render
const shouldRenderNotificationConfigs = useMemo(() => { | ||
return actions.some(v => v !== MultipleCheckboxOptions.EMAIL); | ||
}, [actions]); |
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.
Same here, no need for useMemo
.
use memo should really only be used when we
-
need a stable reference to a value where it would change if it was re-computed on every render: eg if we had
const something = actions.map(...)
This would create a new object every time, and we would want to put it in a memo to stop that from happening
OR
- The computation is expensive. This is actually quite rare.
} | ||
|
||
return ( | ||
<div> |
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.
Is this extra div
needed? Can it just be a fragment
const RuleWrapper = styled('div')` | ||
padding: ${space(1)}; | ||
background-color: ${p => p.theme.backgroundSecondary}; | ||
border-radius: ${p => p.theme.borderRadius}; | ||
`; | ||
|
||
const Rule = styled('div')` | ||
display: flex; | ||
align-items: center; | ||
gap: ${space(1)}; | ||
`; |
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.
Do We need both Rule and RuleWrapper? Seems like we could combine them?
adding an option on the project creation page to send alerts to a messaging integration (Slack, Discord, MS Teams, etc.)
email alert rules are still created as well (the checkbox to "notify via email" is selected by default, and can not be deselected)
if org has no integrations installed:
no-integration-fr.mov
*notice how the dropdowns to select an integration provider (slack, discord, ms teams) and workspace are disabled, because there is only 1 choice for each
if org has multiple integrations installed:
with-integration.mov
if the org is not on a team plan or above, the button is disabled:
if the user has selected "notify via integration" but they have not input a channel ID, the create project button is disabled:
error messages are displayed at the bottom (existing behavior):