diff --git a/grafana-plugin/src/components/Timeline/Timeline.module.css b/grafana-plugin/src/components/Timeline/Timeline.module.css
index b4ee44c7..c2f4e970 100644
--- a/grafana-plugin/src/components/Timeline/Timeline.module.css
+++ b/grafana-plugin/src/components/Timeline/Timeline.module.css
@@ -27,3 +27,7 @@
word-break: break-word;
flex-grow: 1;
}
+
+.content--noMargin {
+ margin: 0;
+}
diff --git a/grafana-plugin/src/components/Timeline/TimelineItem.tsx b/grafana-plugin/src/components/Timeline/TimelineItem.tsx
index 1807cf28..077d1943 100644
--- a/grafana-plugin/src/components/Timeline/TimelineItem.tsx
+++ b/grafana-plugin/src/components/Timeline/TimelineItem.tsx
@@ -33,7 +33,7 @@ const TimelineItem: React.FC = ({
{number}
)}
-
{children}
+
{children}
);
};
diff --git a/grafana-plugin/src/containers/AlertRules/parts/index.tsx b/grafana-plugin/src/containers/AlertRules/parts/index.tsx
index 8a49b500..13268e44 100644
--- a/grafana-plugin/src/containers/AlertRules/parts/index.tsx
+++ b/grafana-plugin/src/containers/AlertRules/parts/index.tsx
@@ -12,10 +12,11 @@ import { getVar } from 'utils/DOM';
interface ChatOpsConnectorsProps {
channelFilterId: ChannelFilter['id'];
+ showLineNumber?: boolean;
}
export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => {
- const { channelFilterId } = props;
+ const { channelFilterId, showLineNumber = true } = props;
const store = useStore();
const { telegramChannelStore } = store;
@@ -29,7 +30,7 @@ export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => {
}
return (
-
+
{isSlackInstalled && }
{isTelegramInstalled && }
diff --git a/grafana-plugin/src/containers/GSelect/GSelect.tsx b/grafana-plugin/src/containers/GSelect/GSelect.tsx
index 1570c443..16fb6db1 100644
--- a/grafana-plugin/src/containers/GSelect/GSelect.tsx
+++ b/grafana-plugin/src/containers/GSelect/GSelect.tsx
@@ -146,7 +146,6 @@ const GSelect = observer((props: GSelectProps) => {
return (
- {/*@ts-ignore*/}
{
+ escalationChainStore.updateItems();
+ }, []);
+
const channelFilter = alertReceiveChannelStore.channelFilters[channelFilterId];
const channelFiltersTotal = Object.keys(alertReceiveChannelStore.channelFilters);
if (!channelFilter) {
@@ -152,7 +156,7 @@ const ExpandedIntegrationRouteDisplay: React.FC
Publish to ChatOps
-
+
)}
@@ -162,18 +166,21 @@ const ExpandedIntegrationRouteDisplay: React.FC
Escalation chain
- ({
+ value: escalationChainStore.items[eschalationChainId].id,
+ label: escalationChainStore.items[eschalationChainId].name,
+ })
+ )}
+ value={channelFilter.escalation_chain}
getOptionLabel={(item: SelectableValue) => {
return (
<>
@@ -185,18 +192,20 @@ const ExpandedIntegrationRouteDisplay: React.FC
);
}}
- />
+ >
-
+
+
+
-
+
+
+
{channelFilter.escalation_chain && (
@@ -240,7 +249,7 @@ const ExpandedIntegrationRouteDisplay: React.FC {
showLineNumbers
useAutoCompleteList={false}
language={MONACO_LANGUAGE.json}
- monacoOptions={MONACO_PAYLOAD_OPTIONS}
+ monacoOptions={{
+ ...MONACO_PAYLOAD_OPTIONS,
+ readOnly: true,
+ }}
/>
diff --git a/grafana-plugin/src/containers/UserSettings/parts/tabs/PhoneVerification/PhoneVerification.tsx b/grafana-plugin/src/containers/UserSettings/parts/tabs/PhoneVerification/PhoneVerification.tsx
index 8393a2c8..578b093f 100644
--- a/grafana-plugin/src/containers/UserSettings/parts/tabs/PhoneVerification/PhoneVerification.tsx
+++ b/grafana-plugin/src/containers/UserSettings/parts/tabs/PhoneVerification/PhoneVerification.tsx
@@ -24,7 +24,8 @@ interface PhoneVerificationProps extends HTMLAttributes {
interface PhoneVerificationState {
phone: string;
code: string;
- isCodeSent: boolean;
+ isCodeSent?: boolean;
+ isPhoneCallInitiated?: boolean;
isPhoneNumberHidden: boolean;
isLoading: boolean;
showForgetScreen: boolean;
@@ -41,7 +42,10 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
const user = userStore.items[userPk];
const isCurrentUser = userStore.currentUserPk === user.pk;
- const [{ showForgetScreen, phone, code, isCodeSent, isPhoneNumberHidden, isLoading }, setState] = useReducer(
+ const [
+ { showForgetScreen, phone, code, isCodeSent, isPhoneCallInitiated, isPhoneNumberHidden, isLoading },
+ setState,
+ ] = useReducer(
(state: PhoneVerificationState, newState: Partial) => ({
...state,
...newState,
@@ -51,6 +55,7 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
phone: user.verified_phone_number || '+',
isLoading: false,
isCodeSent: false,
+ isPhoneCallInitiated: false,
showForgetScreen: false,
isPhoneNumberHidden: user.hide_phone_number,
}
@@ -70,7 +75,7 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
);
const onChangePhoneCallback = useCallback((event: React.ChangeEvent) => {
- setState({ isCodeSent: false, phone: event.target.value });
+ setState({ isCodeSent: false, isPhoneCallInitiated: false, phone: event.target.value });
}, []);
const onChangeCodeCallback = useCallback((event: React.ChangeEvent) => {
@@ -81,51 +86,81 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
userStore.makeTestCall(userPk);
}, [userPk, userStore.makeTestCall]);
+ const handleSendTestSmsClick = useCallback(() => {
+ userStore.sendTestSms(userPk);
+ }, [userPk, userStore.sendTestSms]);
+
const handleForgetNumberClick = useCallback(() => {
userStore.forgetPhone(userPk).then(async () => {
await userStore.loadUser(userPk);
- setState({ phone: '', showForgetScreen: false, isCodeSent: false });
+ setState({ phone: '', showForgetScreen: false, isCodeSent: false, isPhoneCallInitiated: false });
});
}, [userPk, userStore.forgetPhone, userStore.loadUser]);
- const onSubmitCallback = useCallback(async () => {
- if (isCodeSent) {
- userStore.verifyPhone(userPk, code).then(() => {
- userStore.loadUser(userPk);
- });
- } else {
- window.grecaptcha.ready(function () {
- window.grecaptcha
- .execute(rootStore.recaptchaSiteKey, { action: 'mobile_verification_code' })
- .then(async function (token) {
- await userStore.updateUser({
- pk: userPk,
- email: user.email,
- unverified_phone_number: phone,
- });
+ const onSubmitCallback = useCallback(
+ async (type) => {
+ let codeVerification = isCodeSent;
+ if (type === 'verification_call') {
+ codeVerification = isPhoneCallInitiated;
+ }
+ if (codeVerification) {
+ userStore.verifyPhone(userPk, code).then(() => {
+ userStore.loadUser(userPk);
+ });
+ } else {
+ window.grecaptcha.ready(function () {
+ window.grecaptcha
+ .execute(rootStore.recaptchaSiteKey, { action: 'mobile_verification_code' })
+ .then(async function (token) {
+ await userStore.updateUser({
+ pk: userPk,
+ email: user.email,
+ unverified_phone_number: phone,
+ });
- userStore.fetchVerificationCode(userPk, token).then(() => {
- setState({ isCodeSent: true });
-
- if (codeInputRef.current) {
- codeInputRef.current.focus();
+ switch (type) {
+ case 'verification_call':
+ userStore.fetchVerificationCall(userPk, token).then(() => {
+ setState({ isPhoneCallInitiated: true });
+ if (codeInputRef.current) {
+ codeInputRef.current.focus();
+ }
+ });
+ break;
+ case 'verification_sms':
+ userStore.fetchVerificationCode(userPk, token).then(() => {
+ setState({ isCodeSent: true });
+ if (codeInputRef.current) {
+ codeInputRef.current.focus();
+ }
+ });
+ break;
}
});
- });
- });
- }
- }, [
- code,
- isCodeSent,
- phone,
- user.email,
- userPk,
- userStore.verifyPhone,
- userStore.updateUser,
- userStore.fetchVerificationCode,
- ]);
+ });
+ }
+ },
+ [
+ code,
+ isCodeSent,
+ phone,
+ user.email,
+ userPk,
+ userStore.verifyPhone,
+ userStore.updateUser,
+ userStore.fetchVerificationCode,
+ ]
+ );
+
+ const onVerifyCallback = useCallback(async () => {
+ userStore.verifyPhone(userPk, code).then(() => {
+ userStore.loadUser(userPk);
+ });
+ }, [code, userPk, userStore.verifyPhone, userStore.loadUser]);
+
+ const isPhoneProviderConfigured = teamStore.currentTeam?.env_status.phone_provider?.configured;
+ const providerConfiguration = teamStore.currentTeam?.env_status.phone_provider;
- const isTwilioConfigured = teamStore.currentTeam?.env_status.twilio_configured;
const phoneHasMinimumLength = phone?.length > 8;
const isPhoneValid = phoneHasMinimumLength && PHONE_REGEX.test(phone);
@@ -133,7 +168,9 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
const action = isCurrentUser ? UserActions.UserSettingsWrite : UserActions.UserSettingsAdmin;
const isButtonDisabled =
- phone === user.verified_phone_number || (!isCodeSent && !isPhoneValid) || !isTwilioConfigured;
+ phone === user.verified_phone_number ||
+ (!isCodeSent && !isPhoneValid && !isPhoneCallInitiated) ||
+ !isPhoneProviderConfigured;
const isPhoneDisabled = !!user.verified_phone_number;
const isCodeFieldDisabled = !isCodeSent || !isUserActionAllowed(action);
@@ -158,15 +195,15 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
>
)}
- {!isTwilioConfigured && store.hasFeature(AppFeature.LiveSettings) && (
+ {!isPhoneProviderConfigured && store.hasFeature(AppFeature.LiveSettings) && (
<>
- Can't verify phone. Check ENV variables{' '}
- related to Twilio.
+ Can't verify phone. Check ENV variables to
+ configure your provider.
>
}
/>
@@ -185,7 +222,7 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
autoFocus
id="phone"
required
- disabled={!isTwilioConfigured || isPhoneDisabled}
+ disabled={!isPhoneProviderConfigured || isPhoneDisabled}
placeholder="Please enter the phone number with country code, e.g. +12451111111"
// @ts-ignore
prefix={}
@@ -233,11 +270,14 @@ const PhoneVerification = observer((props: PhoneVerificationProps) => {
setState({ showForgetScreen: true })}
user={user}
/>
@@ -273,12 +313,20 @@ interface PhoneVerificationButtonsGroupProps {
action: UserAction;
isCodeSent: boolean;
+ isPhoneCallInitiated: boolean;
isButtonDisabled: boolean;
isTestCallInProgress: boolean;
- isTwilioConfigured: boolean;
-
- onSubmitCallback(): void;
+ providerConfiguration: {
+ configured: boolean;
+ test_call: boolean;
+ test_sms: boolean;
+ verification_call: boolean;
+ verification_sms: boolean;
+ };
+ onSubmitCallback(type: string): void;
+ onVerifyCallback(): void;
handleMakeTestCallClick(): void;
+ handleSendTestSmsClick(): void;
onShowForgetScreen(): void;
user: User;
@@ -287,25 +335,60 @@ interface PhoneVerificationButtonsGroupProps {
function PhoneVerificationButtonsGroup({
action,
isCodeSent,
+ isPhoneCallInitiated,
isButtonDisabled,
isTestCallInProgress,
- isTwilioConfigured,
+ providerConfiguration,
onSubmitCallback,
+ onVerifyCallback,
handleMakeTestCallClick,
+ handleSendTestSmsClick,
onShowForgetScreen,
user,
}: PhoneVerificationButtonsGroupProps) {
const showForgetNumber = !!user.verified_phone_number;
const showVerifyOrSendCodeButton = !user.verified_phone_number;
-
+ const verificationStarted = isCodeSent || isPhoneCallInitiated;
return (
{showVerifyOrSendCodeButton && (
-
-
-
+
+ {verificationStarted ? (
+ <>
+
+
+
+ >
+ ) : (
+
+ {' '}
+ {providerConfiguration.verification_sms && (
+
+
+
+ )}
+ {providerConfiguration.verification_call && (
+
+
+
+ )}
+
+ )}
+
)}
{showForgetNumber && (
@@ -321,24 +404,33 @@ function PhoneVerificationButtonsGroup({
)}
{user.verified_phone_number && (
- <>
-
-
-
-
-
-
- >
+
+ {providerConfiguration.test_sms && (
+
+
+
+ )}
+ {providerConfiguration.test_call && (
+
+
+
+
+
+
+
+
+ )}
+
)}
);
diff --git a/grafana-plugin/src/models/team/team.types.ts b/grafana-plugin/src/models/team/team.types.ts
index cf511c96..9fdc1df6 100644
--- a/grafana-plugin/src/models/team/team.types.ts
+++ b/grafana-plugin/src/models/team/team.types.ts
@@ -66,5 +66,12 @@ export interface Team {
env_status: {
twilio_configured: boolean;
telegram_configured: boolean;
+ phone_provider: {
+ configured: boolean;
+ test_call: boolean;
+ test_sms: boolean;
+ verification_call: boolean;
+ verification_sms: boolean;
+ };
};
}
diff --git a/grafana-plugin/src/models/user/user.ts b/grafana-plugin/src/models/user/user.ts
index 25d8f750..437f392b 100644
--- a/grafana-plugin/src/models/user/user.ts
+++ b/grafana-plugin/src/models/user/user.ts
@@ -245,6 +245,14 @@ export class UserStore extends BaseStore {
}).catch(throttlingError);
}
+ @action
+ async fetchVerificationCall(userPk: User['pk'], recaptchaToken: string) {
+ await makeRequest(`/users/${userPk}/get_verification_call/`, {
+ method: 'GET',
+ headers: { 'X-OnCall-Recaptcha': recaptchaToken },
+ }).catch(throttlingError);
+ }
+
@action
async verifyPhone(userPk: User['pk'], token: string) {
return await makeRequest(`/users/${userPk}/verify_number/?token=${token}`, {
@@ -376,6 +384,18 @@ export class UserStore extends BaseStore {
});
}
+ async sendTestSms(userPk: User['pk']) {
+ this.isTestCallInProgress = true;
+
+ return await makeRequest(`/users/${userPk}/send_test_sms/`, {
+ method: 'POST',
+ })
+ .catch(this.onApiError)
+ .finally(() => {
+ this.isTestCallInProgress = false;
+ });
+ }
+
async getiCalLink(userPk: User['pk']) {
return await makeRequest(`/users/${userPk}/export_token/`, {
method: 'GET',
diff --git a/grafana-plugin/src/plugin.json b/grafana-plugin/src/plugin.json
index cdbfef28..6250bbe8 100644
--- a/grafana-plugin/src/plugin.json
+++ b/grafana-plugin/src/plugin.json
@@ -620,8 +620,7 @@
}
],
"dependencies": {
- "grafanaDependency": ">=8.3.2",
- "grafanaVersion": "8.3",
+ "grafanaDependency": ">=9.2.0",
"plugins": []
}
}
diff --git a/tools/pagerduty-migrator/requirements.txt b/tools/pagerduty-migrator/requirements.txt
index 8e7340c8..36e35a92 100644
--- a/tools/pagerduty-migrator/requirements.txt
+++ b/tools/pagerduty-migrator/requirements.txt
@@ -1,4 +1,4 @@
-requests==2.27.1
+requests==2.31.0
pdpyras==4.5.0
pytest==7.1.2
pytest-env==0.6.2
\ No newline at end of file