Fix Slack channel and user group values not populating on first visit of schedule notification settings (#4671)

# What this PR does

Make sure GSelect gets rerendered with updated items., more context
[here](https://raintank-corp.slack.com/archives/C04JCU51NF8/p1720559481963519)

## Which issue(s) this PR closes

https://github.com/grafana/oncall/issues/4670

<!--
*Note*: if you have more than one GitHub issue that this PR closes, be
sure to preface
each issue link with a [closing
keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue).
This ensures that the issue(s) are auto-closed once the PR has been
merged.
-->

## Checklist

- [ ] Unit, integration, and e2e (if applicable) tests updated
- [x] Documentation added (or `pr:no public docs` PR label added if not
required)
- [x] Added the relevant release notes label (see labels prefixed w/
`release:`). These labels dictate how your PR will
    show up in the autogenerated release notes.
This commit is contained in:
Dominik Broj 2024-07-15 09:15:44 +02:00 committed by GitHub
parent deff52df07
commit 851ca2e12a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 131 additions and 46 deletions

View file

@ -168,7 +168,11 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
isDisabled,
theme,
store: { userStore },
store: {
userStore,
// dereferencing items is needed to rerender GSelect
userStore: { items: userItems },
},
} = this.props;
const { notify_to_users_queue } = data;
const styles = getEscalationPolicyStyles(theme);
@ -187,7 +191,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
onChange={this.getOnChangeHandler('notify_to_users_queue')}
getOptionLabel={({ value }: SelectableValue) => <UserTooltip id={value} />}
width={'auto'}
items={userStore.items}
items={userItems}
fetchItemsFn={userStore.fetchItems}
fetchItemFn={async (id) => await userStore.fetchItemById({ userPk: id, skipIfAlreadyPending: true })}
getSearchResult={() => UserHelper.getSearchResult(userStore)}
@ -352,7 +356,12 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
theme,
isDisabled,
store: { grafanaTeamStore, scheduleStore },
store: {
scheduleStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
scheduleStore: { items: scheduleItems },
},
} = this.props;
const { notify_schedule } = data;
const styles = getEscalationPolicyStyles(theme);
@ -362,7 +371,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<GSelect<Schedule>
allowClear
disabled={isDisabled}
items={scheduleStore.items}
items={scheduleItems}
fetchItemsFn={scheduleStore.updateItems}
fetchItemFn={scheduleStore.updateItem}
getSearchResult={scheduleStore.getSearchResult}
@ -373,7 +382,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
value={notify_schedule}
onChange={this.getOnChangeHandler('notify_schedule')}
getOptionLabel={(item: SelectableValue) => {
const team = grafanaTeamStore.items[scheduleStore.items[item.value].team];
const team = grafanaTeamItems[scheduleStore.items[item.value].team];
return (
<>
<Text>{item.label} </Text>
@ -391,7 +400,11 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
theme,
isDisabled,
store: { userGroupStore },
store: {
userGroupStore,
// dereferencing items is needed to rerender GSelect
userGroupStore: { items: userGroupItems },
},
} = this.props;
const { notify_to_group } = data;
@ -402,7 +415,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<GSelect<UserGroup>
allowClear
disabled={isDisabled}
items={userGroupStore.items}
items={userGroupItems}
fetchItemsFn={userGroupStore.updateItems}
fetchItemFn={userGroupStore.fetchItemById}
getSearchResult={userGroupStore.getSearchResult}
@ -423,7 +436,12 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
data,
theme,
isDisabled,
store: { grafanaTeamStore, outgoingWebhookStore },
store: {
grafanaTeamStore,
outgoingWebhookStore,
// dereferencing items is needed to rerender GSelect
outgoingWebhookStore: { items: outgoingWebhookItems },
},
} = this.props;
const { custom_webhook } = data;
@ -433,7 +451,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<WithPermissionControlTooltip key="custom-webhook" userAction={UserActions.EscalationChainsWrite}>
<GSelect<ApiSchemas['Webhook']>
disabled={isDisabled}
items={outgoingWebhookStore.items}
items={outgoingWebhookItems}
fetchItemsFn={outgoingWebhookStore.updateItems}
fetchItemFn={outgoingWebhookStore.updateItem}
getSearchResult={outgoingWebhookStore.getSearchResult}
@ -466,7 +484,11 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
const {
data,
isDisabled,
store: { grafanaTeamStore },
store: {
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
},
} = this.props;
const { notify_to_team_members } = data;
@ -474,7 +496,7 @@ class _EscalationPolicy extends React.Component<EscalationPolicyProps, any> {
<WithPermissionControlTooltip key="notify_to_team_members" userAction={UserActions.EscalationChainsWrite}>
<GSelect<GrafanaTeam>
disabled={isDisabled}
items={grafanaTeamStore.items}
items={grafanaTeamItems}
fetchItemsFn={grafanaTeamStore.updateItems}
fetchItemFn={grafanaTeamStore.fetchItemById}
getSearchResult={grafanaTeamStore.getSearchResult}

View file

@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { HorizontalGroup, InlineSwitch } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import { GSelect } from 'containers/GSelect/GSelect';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
@ -18,11 +19,16 @@ interface MSTeamsConnectorProps {
channelFilterId: ChannelFilter['id'];
}
export const MSTeamsConnector = (props: MSTeamsConnectorProps) => {
export const MSTeamsConnector = observer((props: MSTeamsConnectorProps) => {
const { channelFilterId } = props;
const store = useStore();
const { alertReceiveChannelStore, msteamsChannelStore } = store;
const {
alertReceiveChannelStore,
msteamsChannelStore,
// dereferencing items is needed to rerender GSelect
msteamsChannelStore: { items: msteamsChannelItems },
} = store;
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
@ -57,7 +63,7 @@ export const MSTeamsConnector = (props: MSTeamsConnectorProps) => {
<GSelect<MSTeamsChannel>
allowClear
className={cx('select', 'control')}
items={msteamsChannelStore.items}
items={msteamsChannelItems}
fetchItemsFn={msteamsChannelStore.updateItems}
fetchItemFn={msteamsChannelStore.updateById}
getSearchResult={msteamsChannelStore.getSearchResult}
@ -71,4 +77,4 @@ export const MSTeamsConnector = (props: MSTeamsConnectorProps) => {
</HorizontalGroup>
</div>
);
};
});

View file

@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { HorizontalGroup, InlineSwitch } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import { GSelect } from 'containers/GSelect/GSelect';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
@ -19,7 +20,7 @@ interface SlackConnectorProps {
channelFilterId: ChannelFilter['id'];
}
export const SlackConnector = (props: SlackConnectorProps) => {
export const SlackConnector = observer((props: SlackConnectorProps) => {
const { channelFilterId } = props;
const store = useStore();
@ -27,6 +28,8 @@ export const SlackConnector = (props: SlackConnectorProps) => {
organizationStore: { currentOrganization },
alertReceiveChannelStore,
slackChannelStore,
// dereferencing items is needed to rerender GSelect
slackChannelStore: { items: slackChannelItems },
} = store;
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
@ -57,7 +60,7 @@ export const SlackConnector = (props: SlackConnectorProps) => {
<GSelect<SlackChannel>
allowClear
className={cx('select', 'control')}
items={slackChannelStore.items}
items={slackChannelItems}
fetchItemsFn={slackChannelStore.updateItems}
fetchItemFn={slackChannelStore.updateItem}
getSearchResult={getSearchResult}
@ -94,4 +97,4 @@ export const SlackConnector = (props: SlackConnectorProps) => {
return results;
}
};
});

View file

@ -2,6 +2,7 @@ import React, { useCallback } from 'react';
import { HorizontalGroup, InlineSwitch } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import { GSelect } from 'containers/GSelect/GSelect';
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
@ -18,9 +19,14 @@ interface TelegramConnectorProps {
channelFilterId: ChannelFilter['id'];
}
export const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) => {
export const TelegramConnector = observer(({ channelFilterId }: TelegramConnectorProps) => {
const store = useStore();
const { alertReceiveChannelStore, telegramChannelStore } = store;
const {
alertReceiveChannelStore,
telegramChannelStore,
// dereferencing items is needed to rerender GSelect
telegramChannelStore: { items: telegramChannelItems },
} = store;
const channelFilter = store.alertReceiveChannelStore.channelFilters[channelFilterId];
@ -49,7 +55,7 @@ export const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) =
<GSelect<TelegramChannel>
allowClear
className={cx('select', 'control')}
items={telegramChannelStore.items}
items={telegramChannelItems}
fetchItemsFn={telegramChannelStore.updateItems}
fetchItemFn={telegramChannelStore.updateById}
getSearchResult={telegramChannelStore.getSearchResult}
@ -63,4 +69,4 @@ export const TelegramConnector = ({ channelFilterId }: TelegramConnectorProps) =
</HorizontalGroup>
</div>
);
};
});

View file

@ -42,7 +42,11 @@ const GroupedAlertNumber = observer(({ value }: GroupedAlertNumberProps) => {
export const AttachIncidentForm = observer(({ id, onUpdate, onHide }: AttachIncidentFormProps) => {
const store = useStore();
const { alertGroupStore } = store;
const {
alertGroupStore,
// dereferencing alerts is needed to rerender GSelect
alertGroupStore: { alerts: alertGroupAlerts },
} = store;
const [selected, setSelected] = useState<ApiSchemas['AlertGroup']['pk']>(undefined);
@ -75,7 +79,7 @@ export const AttachIncidentForm = observer(({ id, onUpdate, onHide }: AttachInci
>
<WithPermissionControlTooltip userAction={UserActions.AlertGroupsWrite}>
<GSelect<ApiSchemas['AlertGroup']>
items={Object.fromEntries(alertGroupStore.alerts)}
items={Object.fromEntries(alertGroupAlerts)}
fetchItemsFn={async (query: string) => {
await alertGroupStore.fetchAlertGroups(false, query);
}}

View file

@ -2,6 +2,7 @@ import React, { ChangeEvent, FC, useCallback, useState } from 'react';
import { Button, Field, HorizontalGroup, Input, Modal } from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import { GSelect } from 'containers/GSelect/GSelect';
import { EscalationChain } from 'models/escalation_chain/escalation_chain.types';
@ -26,11 +27,17 @@ interface EscalationChainFormProps {
const cx = cn.bind(styles);
export const EscalationChainForm: FC<EscalationChainFormProps> = (props) => {
export const EscalationChainForm: FC<EscalationChainFormProps> = observer((props) => {
const { escalationChainId, onHide, onSubmit: onSubmitProp, mode } = props;
const store = useStore();
const { escalationChainStore, userStore, grafanaTeamStore } = store;
const {
escalationChainStore,
userStore,
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
} = store;
const user = userStore.currentUser;
@ -93,7 +100,7 @@ export const EscalationChainForm: FC<EscalationChainFormProps> = (props) => {
<div className={cx('root')}>
<Field label="Assign to team">
<GSelect<GrafanaTeam>
items={grafanaTeamStore.items}
items={grafanaTeamItems}
fetchItemsFn={grafanaTeamStore.updateItems}
fetchItemFn={grafanaTeamStore.fetchItemById}
getSearchResult={grafanaTeamStore.getSearchResult}
@ -125,4 +132,4 @@ export const EscalationChainForm: FC<EscalationChainFormProps> = (props) => {
</div>
</Modal>
);
};
});

View file

@ -25,7 +25,12 @@ export const GrafanaTeamSelect = observer(
({ onSelect, onHide, withoutModal, defaultValue }: GrafanaTeamSelectProps) => {
const store = useStore();
const { userStore, grafanaTeamStore } = store;
const {
userStore,
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
} = store;
const user = userStore.currentUser;
const [selectedTeam, setSelectedTeam] = useState<GrafanaTeam['id']>(defaultValue);
@ -53,7 +58,7 @@ export const GrafanaTeamSelect = observer(
const select = (
<GSelect<GrafanaTeam>
items={grafanaTeamStore.items}
items={grafanaTeamItems}
fetchItemsFn={grafanaTeamStore.updateItems}
fetchItemFn={grafanaTeamStore.fetchItemById}
getSearchResult={grafanaTeamStore.getSearchResult}

View file

@ -97,7 +97,13 @@ export const IntegrationForm = observer(
const history = useHistory();
const styles = useStyles2(getIntegrationFormStyles);
const isNew = id === 'new';
const { userStore, grafanaTeamStore, alertReceiveChannelStore } = store;
const {
userStore,
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
alertReceiveChannelStore,
} = store;
const data: Partial<ApiSchemas['AlertReceiveChannel']> = isNew
? {
@ -234,7 +240,7 @@ export const IntegrationForm = observer(
placeholder="Assign to team"
{...field}
{...{
items: grafanaTeamStore.items,
items: grafanaTeamItems,
fetchItemsFn: grafanaTeamStore.updateItems,
fetchItemFn: grafanaTeamStore.fetchItemById,
getSearchResult: grafanaTeamStore.getSearchResult,

View file

@ -37,7 +37,11 @@ interface FormFields {
export const MaintenanceForm = observer((props: MaintenanceFormProps) => {
const { onUpdate, onHide, initialData = {} } = props;
const { alertReceiveChannelStore } = useStore();
const {
alertReceiveChannelStore,
// dereferencing items is needed to rerender GSelect
alertReceiveChannelStore: { items: alertReceiveChannelItems },
} = useStore();
const onSubmit = useCallback(async (data) => {
try {
@ -87,7 +91,7 @@ export const MaintenanceForm = observer((props: MaintenanceFormProps) => {
>
<GSelect<ApiSchemas['AlertReceiveChannel']>
disabled
items={alertReceiveChannelStore.items}
items={alertReceiveChannelItems}
fetchItemsFn={alertReceiveChannelStore.fetchItems}
fetchItemFn={alertReceiveChannelStore.fetchItemById}
getSearchResult={() => AlertReceiveChannelHelper.getSearchResult(alertReceiveChannelStore)}

View file

@ -48,7 +48,12 @@ const FORWARD_RADIO_OPTIONS = [
export const OutgoingWebhookFormFields: React.FC<OutgoingWebhookFormFieldsProps> = observer(
({ preset, hasLabelsFeature, onTemplateEditClick }) => {
const { grafanaTeamStore, alertReceiveChannelStore } = useStore();
const {
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
alertReceiveChannelStore,
} = useStore();
const { items, fetchItems, fetchItemById } = alertReceiveChannelStore;
const {
control,
@ -98,7 +103,7 @@ export const OutgoingWebhookFormFields: React.FC<OutgoingWebhookFormFieldsProps>
>
<GSelect<GrafanaTeam>
allowClear
items={grafanaTeamStore.items}
items={grafanaTeamItems}
fetchItemsFn={grafanaTeamStore.updateItems}
fetchItemFn={grafanaTeamStore.fetchItemById}
getSearchResult={grafanaTeamStore.getSearchResult}

View file

@ -199,7 +199,11 @@ const FormFields = ({ scheduleType }: { scheduleType: Schedule['type'] }) => {
};
const ScheduleCommonFields = () => {
const { grafanaTeamStore } = useStore();
const {
grafanaTeamStore,
// dereferencing items is needed to rerender GSelect
grafanaTeamStore: { items: grafanaTeamItems },
} = useStore();
const { control, formState } = useFormContext<FormFields>();
const { errors } = formState;
@ -222,7 +226,7 @@ const ScheduleCommonFields = () => {
render={({ field }) => (
<Field label="Assign to team" invalid={!!errors.team} error={errors.team?.message}>
<GSelect<GrafanaTeam>
items={grafanaTeamStore.items}
items={grafanaTeamItems}
fetchItemsFn={grafanaTeamStore.updateItems}
fetchItemFn={grafanaTeamStore.fetchItemById}
getSearchResult={grafanaTeamStore.getSearchResult}
@ -239,12 +243,18 @@ const ScheduleCommonFields = () => {
);
};
const ScheduleNotificationSettingsFields = () => {
const ScheduleNotificationSettingsFields = observer(() => {
const store = useStore();
const styles = useStyles2(getStyles);
const { slackChannelStore, userGroupStore } = store;
const {
slackChannelStore,
userGroupStore,
// dereferencing items is needed to rerender GSelect
slackChannelStore: { items: slackChannelItems },
userGroupStore: { items: userGroupItems },
} = store;
const {
control,
@ -265,7 +275,7 @@ const ScheduleNotificationSettingsFields = () => {
>
<GSelect<SlackChannel>
allowClear
items={slackChannelStore.items}
items={slackChannelItems}
fetchItemsFn={slackChannelStore.updateItems}
fetchItemFn={slackChannelStore.updateItem}
getSearchResult={slackChannelStore.getSearchResult}
@ -291,7 +301,7 @@ const ScheduleNotificationSettingsFields = () => {
>
<GSelect<UserGroup>
allowClear
items={userGroupStore.items}
items={userGroupItems}
fetchItemsFn={userGroupStore.updateItems}
fetchItemFn={userGroupStore.fetchItemById}
getSearchResult={userGroupStore.getSearchResult}
@ -395,7 +405,7 @@ const ScheduleNotificationSettingsFields = () => {
/>
</Collapse>
);
};
});
export const getStyles = () => ({
collapse: css`

View file

@ -19,7 +19,12 @@ import { UserActions } from 'utils/authorization/authorization';
import { DOCS_ROOT } from 'utils/consts';
const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ id }) => {
const { userStore, scheduleStore } = useStore();
const {
userStore,
scheduleStore,
// dereferencing items is needed to rerender GSelect
scheduleStore: { items: scheduleItems },
} = useStore();
const utils = useStyles2(getUtilStyles);
@ -92,7 +97,7 @@ const GoogleCalendar: React.FC<{ id: ApiSchemas['User']['pk'] }> = observer(({ i
isMulti
allowClear
disabled={false}
items={scheduleStore.items}
items={scheduleItems}
fetchItemsFn={scheduleStore.updateItems}
fetchItemFn={scheduleStore.updateItem}
getSearchResult={scheduleStore.getSearchResult}

View file

@ -112,6 +112,8 @@ class _SlackSettings extends Component<SlackProps, SlackState> {
organizationStore: { currentOrganization },
slackStore,
slackChannelStore,
// dereferencing items is needed to rerender GSelect
slackChannelStore: { items: slackChannelItems },
} = store;
return (
@ -126,7 +128,7 @@ class _SlackSettings extends Component<SlackProps, SlackState> {
>
<WithPermissionControlTooltip userAction={UserActions.ChatOpsUpdateSettings}>
<GSelect<SlackChannel>
items={slackChannelStore.items}
items={slackChannelItems}
fetchItemsFn={slackChannelStore.updateItems}
fetchItemFn={slackChannelStore.updateItem}
getSearchResult={slackChannelStore.getSearchResult}