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(onboarding): add messaging integration onboarding to project creation #79194

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

ameliahsu
Copy link
Member

@ameliahsu ameliahsu commented Oct 16, 2024

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:

Screenshot 2024-10-16 at 9 53 32 AM

if the user has selected "notify via integration" but they have not input a channel ID, the create project button is disabled:

Screenshot 2024-10-17 at 11 42 04 AM

error messages are displayed at the bottom (existing behavior):

Screenshot 2024-10-17 at 11 41 43 AM

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Oct 16, 2024
Copy link

codecov bot commented Oct 16, 2024

Bundle Report

Changes will increase total bundle size by 17.37kB (0.06%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
app-webpack-bundle-array-push 30.9MB 17.37kB (0.06%) ⬆️

Copy link

codecov bot commented Oct 16, 2024

Codecov Report

Attention: Patch coverage is 72.81553% with 28 lines in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...s/projectInstall/issueAlertNotificationOptions.tsx 72.00% 14 Missing ⚠️
static/app/views/projectInstall/createProject.tsx 46.66% 8 Missing ⚠️
...s/projectInstall/messagingIntegrationAlertRule.tsx 81.25% 6 Missing ⚠️
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     

Comment on lines 87 to 98
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);
Copy link
Member

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?

Copy link
Member Author

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

Comment on lines 100 to 132
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]);
Copy link
Member

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);
Copy link
Member

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 ?.

Comment on lines 44 to 50
const {
alertNotificationAction: action,
alertNotificationProvider: provider,
setAlertNotificationAction: setAction,
setAlertNotificationIntegration: setIntegration,
setAlertNotificationProvider: setProvider,
} = useIssueAlertNotificationContext();
Copy link
Member

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

Copy link
Member Author

@ameliahsu ameliahsu Oct 16, 2024

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 => {
Copy link
Member

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

Suggested change
const onChange = values => {
const onChange = values => {

</MultipleCheckbox>
{shouldRenderSetupButton && (
<SetupMessagingIntegrationButton
refetchConfigs={refetchConfigs}
Copy link
Member

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

Suggested change
refetchConfigs={refetchConfigs}
refetchConfigs={messagingIntegrationsQuery.refetch}

))}
{Object.keys(errors).map(key => {
const label =
key === 'actions' ? 'Notify via integration' : startCase(key);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Member Author

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 {
Copy link
Member

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

Suggested change
enum MultipleCheckboxOptions {
const enum MultipleCheckboxOptions {

Comment on lines 7 to 16
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;
Copy link
Member

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 {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returning {} loses the type

Suggested change
return {};
return map;

Comment on lines 158 to 163
if (
organization.features.includes(
'messaging-integration-onboarding-project-creation'
) &&
shouldCreateRule
) {
Copy link
Member

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

Copy link
Member

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

Copy link
Member

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,
Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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

Comment on lines 91 to 98
actions: alertNotificationActions,
provider,
integration,
channel,
setActions: setAlertNotificationActions,
setProvider,
setIntegration,
setChannel,
Copy link
Member

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

Suggested change
actions: alertNotificationActions,
provider,
integration,
channel,
setActions: setAlertNotificationActions,
setProvider,
setIntegration,
setChannel,
...notificationProps,

Comment on lines 406 to 415
notificationProps={{
actions: alertNotificationActions,
channel,
integration,
provider,
setActions: setAlertNotificationActions,
setChannel,
setIntegration,
setProvider,
}}
Copy link
Member

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

Suggested change
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,
Copy link
Member

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?

Comment on lines 474 to 475
const label =
key === 'actions' ? t('Notify via integration') : startCase(key);
Copy link
Member

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

Comment on lines 78 to 87
// 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();
// });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment

}

return (
<div>
Copy link
Member

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"
Copy link
Member

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?

Copy link
Member Author

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 😵‍💫

Copy link
Member

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 😥

Comment on lines 63 to 95
<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>
Copy link
Member

@evanpurkhiser evanpurkhiser Oct 17, 2024

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

Suggested change
<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'),
};

Copy link
Member Author

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 😔

Copy link
Member

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?

Comment on lines +148 to +157
const ruleData = await createNotificationAction({
shouldCreateRule,
name,
projectSlug: projectData.slug,
conditions,
actionMatch,
frequency,
});
if (ruleData) {
ruleIds.push(ruleData.id);
Copy link
Member

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)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{keyToErrorText[key] ? keyToErrorText[key] : startCase(key)}
{keyToErrorText[key] ?? startCase(key)}

>
<Wrapper>
<MultipleCheckbox.Item value={MultipleCheckboxOptions.EMAIL} disabled>
Notify via email
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Notify via email
{t('Notify via email')}

{!shouldRenderSetupButton && provider && (
<div>
<MultipleCheckbox.Item value={MultipleCheckboxOptions.INTEGRATION}>
Notify via integration (Slack, Discord, MS Teams, etc.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Notify via integration (Slack, Discord, MS Teams, etc.)
{t('Notify via integration (Slack, Discord, MS Teams, etc.)')}

Comment on lines +252 to +255
<MessagingIntegrationAlertRule
notificationProps={notificationProps}
providersToIntegrations={providersToIntegrations}
/>
Copy link
Member

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?

Copy link
Member Author

@ameliahsu ameliahsu Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put it outside to avoid this centering:

image

because ideally we want the integration alert configuations to be underneath the checkbox like this:

Screenshot 2024-10-17 at 12 59 29 PM

Copy link
Member

@evanpurkhiser evanpurkhiser Oct 17, 2024

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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
placeholder: 'channel ID or URL',
placeholder: t('channel ID or URL'),

forgot these

),
},
discord: {
name: 'Discord',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
name: 'Discord',
name: t('Discord'),

msteams: {
name: 'MSTeams',
action: IssueAlertActionType.MS_TEAMS,
placeholder: 'channel ID',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
placeholder: 'channel ID',
placeholder: t('channel ID'),

),
},
msteams: {
name: 'MSTeams',
Copy link
Member

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 :(

Suggested change
name: 'MSTeams',
name: t('Teams'),

maybe better is actually

Suggested change
name: 'MSTeams',
name: t('MS Teams'),

Copy link
Member Author

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;
Copy link
Member

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

Comment on lines +189 to +193
export default function IssueAlertNotificationOptions(
notificationProps: IssueAlertNotificationProps
) {
const organization = useOrganization();
const {actions, provider, setActions, setIntegration, setProvider} = notificationProps;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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();

Copy link
Member Author

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[]} = {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const map: {[key: string]: OrganizationIntegration[]} = {};
const map: Record<string, OrganizationIntegration[]> = {};

Copy link
Member

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);
Copy link
Member

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?

Copy link
Member

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?

Copy link
Member

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[]>(
Copy link
Member

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?

Comment on lines +223 to +225
const shouldRenderSetupButton = useMemo(() => {
return messagingIntegrationsQuery.data?.every(i => i.status !== 'active');
}, [messagingIntegrationsQuery.data]);
Copy link
Member

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

Comment on lines +227 to +229
const shouldRenderNotificationConfigs = useMemo(() => {
return actions.some(v => v !== MultipleCheckboxOptions.EMAIL);
}, [actions]);
Copy link
Member

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

  1. 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

  1. The computation is expensive. This is actually quite rare.

}

return (
<div>
Copy link
Member

@evanpurkhiser evanpurkhiser Oct 17, 2024

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

Comment on lines +103 to +113
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)};
`;
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Scope: Frontend Automatically applied to PRs that change frontend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants