mirror of https://github.com/grafana/grafana.git
Merge remote-tracking branch 'origin/main' into query-history-app
CodeQL checks / Detect whether code changed (push) Waiting to run
Details
CodeQL checks / Analyze (actions) (push) Blocked by required conditions
Details
CodeQL checks / Analyze (go) (push) Blocked by required conditions
Details
CodeQL checks / Analyze (javascript) (push) Blocked by required conditions
Details
CodeQL checks / Detect whether code changed (push) Waiting to run
Details
CodeQL checks / Analyze (actions) (push) Blocked by required conditions
Details
CodeQL checks / Analyze (go) (push) Blocked by required conditions
Details
CodeQL checks / Analyze (javascript) (push) Blocked by required conditions
Details
This commit is contained in:
commit
4fd234861c
|
@ -11,6 +11,8 @@ permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
name: Feature toggles documentation is in sync with source
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
|
@ -149,19 +149,11 @@ jobs:
|
||||||
needs:
|
needs:
|
||||||
- build-grafana
|
- build-grafana
|
||||||
steps:
|
steps:
|
||||||
- id: vault-secrets
|
- id: get-github-token
|
||||||
uses: grafana/shared-workflows/actions/get-vault-secrets@main
|
name: "create github app token"
|
||||||
|
uses: grafana/shared-workflows/actions/create-github-app-token@eb02241ed0a92aff205feab8ac3afcdf51c757c8 # create-github-app-token-v0.2.0
|
||||||
with:
|
with:
|
||||||
repo_secrets: |
|
github_app: "delivery-bot-app"
|
||||||
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
|
|
||||||
- name: Generate token
|
|
||||||
id: generate_token
|
|
||||||
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
|
|
||||||
with:
|
|
||||||
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
|
|
||||||
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
|
|
||||||
repositories: '["grafana"]'
|
|
||||||
permissions: '{"checks": "write"}'
|
|
||||||
- uses: grafana/shared-workflows/actions/login-to-gar@main
|
- uses: grafana/shared-workflows/actions/login-to-gar@main
|
||||||
id: login-to-gar
|
id: login-to-gar
|
||||||
with:
|
with:
|
||||||
|
@ -184,7 +176,7 @@ jobs:
|
||||||
echo "IMAGE=${DOCKER_IMAGE}" >> "$GITHUB_ENV"
|
echo "IMAGE=${DOCKER_IMAGE}" >> "$GITHUB_ENV"
|
||||||
- name: Add PR status check
|
- name: Add PR status check
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
GH_TOKEN: ${{ steps.get-github-token.outputs.token }}
|
||||||
SHA: ${{ github.event.pull_request.head.sha }}
|
SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
run: |
|
run: |
|
||||||
gh api \
|
gh api \
|
||||||
|
|
|
@ -468,7 +468,7 @@ export interface FeatureToggles {
|
||||||
*/
|
*/
|
||||||
alertingSaveStatePeriodic?: boolean;
|
alertingSaveStatePeriodic?: boolean;
|
||||||
/**
|
/**
|
||||||
* Enables the compressed protobuf-based alert state storage
|
* Enables the compressed protobuf-based alert state storage. Default is enabled.
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
alertingSaveStateCompressed?: boolean;
|
alertingSaveStateCompressed?: boolean;
|
||||||
|
|
|
@ -789,7 +789,7 @@ var (
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "alertingSaveStateCompressed",
|
Name: "alertingSaveStateCompressed",
|
||||||
Description: "Enables the compressed protobuf-based alert state storage",
|
Description: "Enables the compressed protobuf-based alert state storage. Default is enabled.",
|
||||||
Stage: FeatureStagePublicPreview,
|
Stage: FeatureStagePublicPreview,
|
||||||
FrontendOnly: false,
|
FrontendOnly: false,
|
||||||
Owner: grafanaAlertingSquad,
|
Owner: grafanaAlertingSquad,
|
||||||
|
|
|
@ -428,7 +428,7 @@ const (
|
||||||
FlagAlertingSaveStatePeriodic = "alertingSaveStatePeriodic"
|
FlagAlertingSaveStatePeriodic = "alertingSaveStatePeriodic"
|
||||||
|
|
||||||
// FlagAlertingSaveStateCompressed
|
// FlagAlertingSaveStateCompressed
|
||||||
// Enables the compressed protobuf-based alert state storage
|
// Enables the compressed protobuf-based alert state storage. Default is enabled.
|
||||||
FlagAlertingSaveStateCompressed = "alertingSaveStateCompressed"
|
FlagAlertingSaveStateCompressed = "alertingSaveStateCompressed"
|
||||||
|
|
||||||
// FlagScopeApi
|
// FlagScopeApi
|
||||||
|
|
|
@ -555,14 +555,14 @@
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "alertingSaveStateCompressed",
|
"name": "alertingSaveStateCompressed",
|
||||||
"resourceVersion": "1754657532777",
|
"resourceVersion": "1759485036332",
|
||||||
"creationTimestamp": "2025-01-27T17:47:33Z",
|
"creationTimestamp": "2025-01-27T17:47:33Z",
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"grafana.app/updatedTimestamp": "2025-08-08 12:52:12.777935 +0000 UTC"
|
"grafana.app/updatedTimestamp": "2025-10-03 09:50:36.332762 +0000 UTC"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spec": {
|
"spec": {
|
||||||
"description": "Enables the compressed protobuf-based alert state storage",
|
"description": "Enables the compressed protobuf-based alert state storage. Default is enabled.",
|
||||||
"stage": "preview",
|
"stage": "preview",
|
||||||
"codeowner": "@grafana/alerting-squad",
|
"codeowner": "@grafana/alerting-squad",
|
||||||
"expression": "true"
|
"expression": "true"
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
import 'core-js/stable/structured-clone';
|
||||||
|
import { FormProvider, useForm } from 'react-hook-form';
|
||||||
|
import { clickSelectOption } from 'test/helpers/selectOptionInTest';
|
||||||
|
import { render } from 'test/test-utils';
|
||||||
|
import { byRole, byTestId } from 'testing-library-selector';
|
||||||
|
|
||||||
|
import { grafanaAlertNotifiers } from 'app/features/alerting/unified/mockGrafanaNotifiers';
|
||||||
|
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
||||||
|
|
||||||
|
import { ChannelSubForm } from './ChannelSubForm';
|
||||||
|
import { GrafanaCommonChannelSettings } from './GrafanaCommonChannelSettings';
|
||||||
|
import { Notifier } from './notifiers';
|
||||||
|
|
||||||
|
type TestChannelValues = {
|
||||||
|
__id: string;
|
||||||
|
type: string;
|
||||||
|
settings: Record<string, unknown>;
|
||||||
|
secureFields: Record<string, boolean>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TestReceiverFormValues = {
|
||||||
|
name: string;
|
||||||
|
items: TestChannelValues[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ui = {
|
||||||
|
typeSelector: byTestId('items.0.type'),
|
||||||
|
settings: {
|
||||||
|
webhook: {
|
||||||
|
url: byRole('textbox', { name: /^URL/ }),
|
||||||
|
optionalSettings: byRole('button', { name: /optional webhook settings/i }),
|
||||||
|
title: {
|
||||||
|
container: byTestId('items.0.settings.title'),
|
||||||
|
input: byRole('textbox', { name: /^Title/ }),
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
container: byTestId('items.0.settings.message'),
|
||||||
|
input: byRole('textbox', { name: /^Message/ }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
slack: {
|
||||||
|
recipient: byTestId('items.0.settings.recipient'),
|
||||||
|
token: byTestId('items.0.settings.token'),
|
||||||
|
username: byTestId('items.0.settings.username'),
|
||||||
|
webhookUrl: byRole('textbox', { name: /^Webhook URL/ }),
|
||||||
|
},
|
||||||
|
googlechat: {
|
||||||
|
optionalSettings: byRole('button', { name: /optional google hangouts chat settings/i }),
|
||||||
|
url: byRole('textbox', { name: /^URL/ }),
|
||||||
|
title: {
|
||||||
|
input: byRole('textbox', { name: /^Title/ }),
|
||||||
|
container: byTestId('items.0.settings.title'),
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
input: byRole('textbox', { name: /^Message/ }),
|
||||||
|
container: byTestId('items.0.settings.message'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifiers: Notifier[] = [
|
||||||
|
{ dto: grafanaAlertNotifiers.webhook, meta: { enabled: true, order: 1 } },
|
||||||
|
{ dto: grafanaAlertNotifiers.slack, meta: { enabled: true, order: 2 } },
|
||||||
|
{ dto: grafanaAlertNotifiers.googlechat, meta: { enabled: true, order: 3 } },
|
||||||
|
{ dto: grafanaAlertNotifiers.sns, meta: { enabled: true, order: 4 } },
|
||||||
|
{ dto: grafanaAlertNotifiers.oncall, meta: { enabled: true, order: 5 } },
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('ChannelSubForm', () => {
|
||||||
|
function TestFormWrapper({ defaults, initial }: { defaults: TestChannelValues; initial?: TestChannelValues }) {
|
||||||
|
const form = useForm<TestReceiverFormValues>({
|
||||||
|
defaultValues: {
|
||||||
|
name: 'test-contact-point',
|
||||||
|
items: [defaults],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertmanagerProvider accessType="notification">
|
||||||
|
<FormProvider {...form}>
|
||||||
|
<ChannelSubForm
|
||||||
|
defaultValues={defaults}
|
||||||
|
initialValues={initial}
|
||||||
|
pathPrefix={`items.0.`}
|
||||||
|
integrationIndex={0}
|
||||||
|
notifiers={notifiers}
|
||||||
|
onDuplicate={jest.fn()}
|
||||||
|
commonSettingsComponent={GrafanaCommonChannelSettings}
|
||||||
|
isEditable={true}
|
||||||
|
isTestable={false}
|
||||||
|
/>
|
||||||
|
</FormProvider>
|
||||||
|
</AlertmanagerProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderForm(defaults: TestChannelValues, initial?: TestChannelValues) {
|
||||||
|
return render(<TestFormWrapper defaults={defaults} initial={initial} />);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('switching type hides prior fields and shows new ones', async () => {
|
||||||
|
renderForm({
|
||||||
|
__id: 'id-0',
|
||||||
|
type: 'webhook',
|
||||||
|
settings: { url: '' },
|
||||||
|
secureFields: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Webhook');
|
||||||
|
|
||||||
|
expect(ui.settings.webhook.url.get()).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(ui.settings.slack.recipient.query()).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
await clickSelectOption(ui.typeSelector.get(), 'Slack');
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Slack');
|
||||||
|
|
||||||
|
expect(ui.settings.slack.recipient.get()).toBeInTheDocument();
|
||||||
|
expect(ui.settings.slack.token.get()).toBeInTheDocument();
|
||||||
|
expect(ui.settings.slack.username.get()).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear secure fields when switching integration types', async () => {
|
||||||
|
const googlechatDefaults: TestChannelValues = {
|
||||||
|
__id: 'id-0',
|
||||||
|
type: 'googlechat',
|
||||||
|
settings: { title: 'Alert Title', message: 'Alert Message' },
|
||||||
|
secureFields: { url: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { user } = renderForm(googlechatDefaults, googlechatDefaults);
|
||||||
|
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Google Hangouts Chat');
|
||||||
|
|
||||||
|
expect(ui.settings.googlechat.url.get()).toBeDisabled();
|
||||||
|
expect(ui.settings.googlechat.url.get()).toHaveValue('configured');
|
||||||
|
|
||||||
|
await user.click(ui.settings.googlechat.optionalSettings.get());
|
||||||
|
|
||||||
|
expect(ui.settings.googlechat.title.input.get()).toHaveValue('Alert Title');
|
||||||
|
expect(ui.settings.googlechat.message.input.get()).toHaveValue('Alert Message');
|
||||||
|
|
||||||
|
await clickSelectOption(ui.typeSelector.get(), 'Webhook');
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Webhook');
|
||||||
|
|
||||||
|
// Webhook URL field should now be present and empty (settings cleared)
|
||||||
|
expect(ui.settings.webhook.url.get()).toHaveValue('');
|
||||||
|
expect(ui.settings.webhook.title.container.get()).toBeInTheDocument();
|
||||||
|
expect(ui.settings.webhook.message.container.get()).toBeInTheDocument();
|
||||||
|
|
||||||
|
// If value for templated fields is empty the input should not be present
|
||||||
|
expect(ui.settings.webhook.message.input.query()).not.toBeInTheDocument();
|
||||||
|
expect(ui.settings.webhook.title.input.query()).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear settings when switching from webhook to googlechat', async () => {
|
||||||
|
const webhookDefaults: TestChannelValues = {
|
||||||
|
__id: 'id-0',
|
||||||
|
type: 'webhook',
|
||||||
|
settings: { url: 'https://example.com/webhook', title: 'Webhook Title', message: 'Webhook Message' },
|
||||||
|
secureFields: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const { user } = renderForm(webhookDefaults, webhookDefaults);
|
||||||
|
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Webhook');
|
||||||
|
|
||||||
|
expect(ui.settings.webhook.url.get()).toHaveValue('https://example.com/webhook');
|
||||||
|
|
||||||
|
await user.click(ui.settings.webhook.optionalSettings.get());
|
||||||
|
expect(ui.settings.webhook.title.input.get()).toHaveValue('Webhook Title');
|
||||||
|
expect(ui.settings.webhook.message.input.get()).toHaveValue('Webhook Message');
|
||||||
|
|
||||||
|
await clickSelectOption(ui.typeSelector.get(), 'Google Hangouts Chat');
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Google Hangouts Chat');
|
||||||
|
|
||||||
|
// Google Chat URL field should now be present and empty (settings cleared)
|
||||||
|
expect(ui.settings.googlechat.url.get()).toHaveValue('');
|
||||||
|
expect(ui.settings.googlechat.title.container.get()).toBeInTheDocument();
|
||||||
|
expect(ui.settings.googlechat.message.container.get()).toBeInTheDocument();
|
||||||
|
|
||||||
|
// If value for templated fields is empty the input should not be present
|
||||||
|
expect(ui.settings.googlechat.message.input.query()).not.toBeInTheDocument();
|
||||||
|
expect(ui.settings.googlechat.title.input.query()).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should restore initial values when switching back to original type', async () => {
|
||||||
|
const googlechatDefaults: TestChannelValues = {
|
||||||
|
__id: 'id-0',
|
||||||
|
type: 'googlechat',
|
||||||
|
settings: { title: 'Original Title', message: 'Original Message' },
|
||||||
|
secureFields: { url: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
const { user } = renderForm(googlechatDefaults, googlechatDefaults);
|
||||||
|
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Google Hangouts Chat');
|
||||||
|
|
||||||
|
expect(ui.settings.googlechat.url.get()).toBeDisabled();
|
||||||
|
expect(ui.settings.googlechat.url.get()).toHaveValue('configured');
|
||||||
|
|
||||||
|
await user.click(ui.settings.googlechat.optionalSettings.get());
|
||||||
|
|
||||||
|
expect(ui.settings.googlechat.title.input.get()).toHaveValue('Original Title');
|
||||||
|
expect(ui.settings.googlechat.message.input.get()).toHaveValue('Original Message');
|
||||||
|
|
||||||
|
// Switch to a different type
|
||||||
|
await clickSelectOption(ui.typeSelector.get(), 'Webhook');
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Webhook');
|
||||||
|
expect(ui.settings.webhook.url.get()).toHaveValue('');
|
||||||
|
|
||||||
|
// Switch back to the original type
|
||||||
|
await clickSelectOption(ui.typeSelector.get(), 'Google Hangouts Chat');
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Google Hangouts Chat');
|
||||||
|
|
||||||
|
// Original settings and secure fields should be restored
|
||||||
|
expect(ui.settings.googlechat.url.get()).toBeDisabled();
|
||||||
|
expect(ui.settings.googlechat.url.get()).toHaveValue('configured');
|
||||||
|
|
||||||
|
expect(ui.settings.googlechat.title.input.get()).toHaveValue('Original Title');
|
||||||
|
expect(ui.settings.googlechat.message.input.get()).toHaveValue('Original Message');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should maintain secure field isolation across multiple type switches', async () => {
|
||||||
|
const googlechatDefaults: TestChannelValues = {
|
||||||
|
__id: 'id-0',
|
||||||
|
type: 'googlechat',
|
||||||
|
settings: {},
|
||||||
|
secureFields: { url: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
renderForm(googlechatDefaults, googlechatDefaults);
|
||||||
|
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Google Hangouts Chat');
|
||||||
|
expect(ui.settings.googlechat.url.get()).toBeDisabled();
|
||||||
|
expect(ui.settings.googlechat.url.get()).toHaveValue('configured');
|
||||||
|
|
||||||
|
// Switch to Slack
|
||||||
|
await clickSelectOption(ui.typeSelector.get(), 'Slack');
|
||||||
|
expect(ui.typeSelector.get()).toHaveTextContent('Slack');
|
||||||
|
|
||||||
|
// Slack should not have any secure fields from Google Chat
|
||||||
|
const slackUrl = ui.settings.slack.webhookUrl.get();
|
||||||
|
expect(slackUrl).toBeEnabled();
|
||||||
|
expect(slackUrl).toHaveValue('');
|
||||||
|
});
|
||||||
|
});
|
|
@ -62,6 +62,7 @@ export function ChannelSubForm<R extends ChannelValues>({
|
||||||
const channelFieldPath = `items.${integrationIndex}` as const;
|
const channelFieldPath = `items.${integrationIndex}` as const;
|
||||||
const typeFieldPath = `${channelFieldPath}.type` as const;
|
const typeFieldPath = `${channelFieldPath}.type` as const;
|
||||||
const settingsFieldPath = `${channelFieldPath}.settings` as const;
|
const settingsFieldPath = `${channelFieldPath}.settings` as const;
|
||||||
|
const secureFieldsPath = `${channelFieldPath}.secureFields` as const;
|
||||||
|
|
||||||
const selectedType = watch(typeFieldPath) ?? defaultValues.type;
|
const selectedType = watch(typeFieldPath) ?? defaultValues.type;
|
||||||
const parse_mode = watch(`${settingsFieldPath}.parse_mode`);
|
const parse_mode = watch(`${settingsFieldPath}.parse_mode`);
|
||||||
|
@ -83,10 +84,28 @@ export function ChannelSubForm<R extends ChannelValues>({
|
||||||
// Restore values when switching back from a changed integration to the default one
|
// Restore values when switching back from a changed integration to the default one
|
||||||
const subscription = watch((formValues, { name, type }) => {
|
const subscription = watch((formValues, { name, type }) => {
|
||||||
// @ts-expect-error name is valid key for formValues
|
// @ts-expect-error name is valid key for formValues
|
||||||
const value = name ? formValues[name] : '';
|
const value = name ? getValues(name, formValues) : '';
|
||||||
if (initialValues && name === typeFieldPath && value === initialValues.type && type === 'change') {
|
if (initialValues && name === typeFieldPath && value === initialValues.type && type === 'change') {
|
||||||
setValue(settingsFieldPath, initialValues.settings);
|
setValue(settingsFieldPath, initialValues.settings);
|
||||||
|
setValue(secureFieldsPath, initialValues.secureFields);
|
||||||
|
} else if (name === typeFieldPath && type === 'change') {
|
||||||
|
// When switching to a new notifier, set the default settings to remove all existing settings
|
||||||
|
// from the previous notifier
|
||||||
|
const newNotifier = notifiers.find(({ dto: { type } }) => type === value);
|
||||||
|
const defaultNotifierSettings = newNotifier ? getDefaultNotifierSettings(newNotifier) : {};
|
||||||
|
|
||||||
|
// Not sure why, but verriding settingsFieldPath is not enough if notifiers have the same settings fields, like url, title
|
||||||
|
const currentSettings = getValues(settingsFieldPath) ?? {};
|
||||||
|
Object.keys(currentSettings).forEach((key) => {
|
||||||
|
if (!defaultNotifierSettings[key]) {
|
||||||
|
setValue(`${settingsFieldPath}.${key}`, defaultNotifierSettings[key]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setValue(settingsFieldPath, defaultNotifierSettings);
|
||||||
|
setValue(secureFieldsPath, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore initial value of an existing oncall integration
|
// Restore initial value of an existing oncall integration
|
||||||
if (
|
if (
|
||||||
initialValues &&
|
initialValues &&
|
||||||
|
@ -98,7 +117,19 @@ export function ChannelSubForm<R extends ChannelValues>({
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
}, [selectedType, initialValues, setValue, settingsFieldPath, typeFieldPath, watch]);
|
}, [
|
||||||
|
selectedType,
|
||||||
|
initialValues,
|
||||||
|
setValue,
|
||||||
|
settingsFieldPath,
|
||||||
|
typeFieldPath,
|
||||||
|
secureFieldsPath,
|
||||||
|
getValues,
|
||||||
|
watch,
|
||||||
|
defaultValues.settings,
|
||||||
|
defaultValues.secureFields,
|
||||||
|
notifiers,
|
||||||
|
]);
|
||||||
|
|
||||||
const onResetSecureField = (key: string) => {
|
const onResetSecureField = (key: string) => {
|
||||||
// formSecureFields might not be up to date if this function is called multiple times in a row
|
// formSecureFields might not be up to date if this function is called multiple times in a row
|
||||||
|
@ -294,6 +325,16 @@ export function ChannelSubForm<R extends ChannelValues>({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDefaultNotifierSettings(notifier: Notifier): Record<string, string> {
|
||||||
|
const defaultSettings: Record<string, string> = {};
|
||||||
|
notifier.dto.options.forEach((option) => {
|
||||||
|
if (option.defaultValue?.value) {
|
||||||
|
defaultSettings[option.propertyName] = option.defaultValue?.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return defaultSettings;
|
||||||
|
}
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
buttons: css({
|
buttons: css({
|
||||||
'& > * + *': {
|
'& > * + *': {
|
||||||
|
|
|
@ -387,7 +387,7 @@ describe('GrafanaReceiverForm', () => {
|
||||||
await user.click(newIntegrationRadio.get());
|
await user.click(newIntegrationRadio.get());
|
||||||
expect(newIntegrationRadio.get()).toBeChecked();
|
expect(newIntegrationRadio.get()).toBeChecked();
|
||||||
|
|
||||||
await user.type(ui.newOnCallIntegrationName.get(), 'emea-oncall');
|
await user.type(await ui.newOnCallIntegrationName.find(), 'emea-oncall');
|
||||||
|
|
||||||
// eslint-disable-next-line testing-library/no-node-access
|
// eslint-disable-next-line testing-library/no-node-access
|
||||||
expect(ui.integrationType.get().closest('form')).toHaveFormValues({
|
expect(ui.integrationType.get().closest('form')).toHaveFormValues({
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { FC, useEffect } from 'react';
|
import { FC } from 'react';
|
||||||
import { Controller, DeepMap, FieldError, useFormContext } from 'react-hook-form';
|
import { Controller, DeepMap, FieldError, useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
@ -116,7 +116,7 @@ const OptionInput: FC<Props & { id: string }> = ({
|
||||||
getOptionMeta,
|
getOptionMeta,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
const { control, register, unregister, setValue } = useFormContext();
|
const { control, register, setValue } = useFormContext();
|
||||||
|
|
||||||
const optionMeta = getOptionMeta?.(option);
|
const optionMeta = getOptionMeta?.(option);
|
||||||
|
|
||||||
|
@ -125,14 +125,6 @@ const OptionInput: FC<Props & { id: string }> = ({
|
||||||
const secureFieldKey = option.secure && option.secureFieldKey ? option.secureFieldKey : '';
|
const secureFieldKey = option.secure && option.secureFieldKey ? option.secureFieldKey : '';
|
||||||
const isEncryptedInput = secureFieldKey && secureFields?.[secureFieldKey];
|
const isEncryptedInput = secureFieldKey && secureFields?.[secureFieldKey];
|
||||||
|
|
||||||
// workaround for https://github.com/react-hook-form/react-hook-form/issues/4993#issuecomment-829012506
|
|
||||||
useEffect(
|
|
||||||
() => () => {
|
|
||||||
unregister(name, { keepValue: false });
|
|
||||||
},
|
|
||||||
[unregister, name]
|
|
||||||
);
|
|
||||||
|
|
||||||
const useTemplates = option.placeholder.includes('{{ template');
|
const useTemplates = option.placeholder.includes('{{ template');
|
||||||
|
|
||||||
function onSelectTemplate(template: string) {
|
function onSelectTemplate(template: string) {
|
||||||
|
|
Loading…
Reference in New Issue