Transformations: Fix runtime handling of old format in Add field from calc / binary (#101455)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
pull/92618/head
Oscar Kilhed 5 months ago committed by Alexander Akhmetov
parent e8b035a5f7
commit 89ff3deb3f
No known key found for this signature in database
GPG Key ID: A5A8947133B1B31B
  1. 35
      docs/sources/administration/provisioning/index.md
  2. 2
      docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md
  3. 314
      packages/grafana-data/src/transformations/transformers/calculateField.test.ts
  4. 5
      packages/grafana-data/src/transformations/transformers/calculateField.ts
  5. 21
      pkg/services/ngalert/api/tooling/definitions/contact_points.go
  6. 7
      pkg/services/ngalert/notifier/channels_config/available_channels.go

@ -503,26 +503,27 @@ The following sections detail the supported settings and secure settings for eac
#### Alert notification `MQTT`
| Name | Secure setting |
| ------------- | -------------- |
| brokerUrl | |
| clientId | |
| topic | |
| messageFormat | |
| username | |
| password | yes |
| retain | |
| qos | |
| tlsConfig | |
| Name | Secure setting |
| -------------------- | -------------- |
| `brokerUrl` | |
| `clientId` | |
| `topic` | |
| `messageFormat` | |
| `username` | |
| `password` | yes |
| `retain` | |
| `qos` | |
| `tlsConfig` | |
| `addGroupKeyToTopic` | |
##### TLS config
| Name | Secure setting |
| ------------------ | -------------- |
| insecureSkipVerify | |
| clientCertificate | yes |
| clientKey | yes |
| caCertificate | yes |
| Name | Secure setting |
| -------------------- | -------------- |
| `insecureSkipVerify` | |
| `clientCertificate` | yes |
| `clientKey` | yes |
| `caCertificate` | yes |
#### Alert notification `pagerduty`

@ -376,6 +376,8 @@ settings:
clientKey: key in PEM format
# <string>
caCertificate: CA certificate in PEM format
# <bool>
addGroupKeyToTopic: false
```
{{< /collapse >}}

@ -237,37 +237,29 @@ describe('calculateField transformer w/ timeseries', () => {
await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith((received) => {
const data = received[0];
expect(data).toMatchInlineSnapshot(`
[
{
"fields": [
{
"config": {},
"name": "TheTime",
"state": {
"displayName": "TheTime",
"multipleFrames": true,
},
"type": "time",
"values": [
1000,
2000,
],
},
{
"config": {},
"name": "B + 2",
"type": "number",
"values": [
4,
202,
],
expect(data).toEqual([
{
fields: [
{
config: {},
name: 'TheTime',
state: {
displayName: 'TheTime',
multipleFrames: true,
},
],
"length": 2,
},
]
`);
type: 'time',
values: [1000, 2000],
},
{
config: {},
name: 'B + 2',
type: 'number',
values: [4, 202],
},
],
length: 2,
},
]);
});
});
@ -321,65 +313,48 @@ describe('calculateField transformer w/ timeseries', () => {
await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith((received) => {
const data = received[0];
expect(data).toMatchInlineSnapshot(`
[
{
"fields": [
{
"config": {},
"name": "TheTime",
"type": "time",
"values": [
1000,
2000,
],
},
{
"config": {},
"name": "A + 2",
"type": "number",
"values": [
3,
102,
],
},
],
"length": 2,
},
{
"fields": [
{
"config": {},
"name": "TheTime",
"type": "time",
"values": [
1000,
2000,
],
},
{
"config": {},
"name": "B + 2",
"type": "number",
"values": [
4,
202,
],
},
{
"config": {},
"name": "C + 2",
"type": "number",
"values": [
5,
302,
],
},
],
"length": 2,
},
]
`);
expect(data).toEqual([
{
fields: [
{
config: {},
name: 'TheTime',
type: 'time',
values: [1000, 2000],
},
{
config: {},
name: 'A + 2',
type: 'number',
values: [3, 102],
},
],
length: 2,
},
{
fields: [
{
config: {},
name: 'TheTime',
type: 'time',
values: [1000, 2000],
},
{
config: {},
name: 'B + 2',
type: 'number',
values: [4, 202],
},
{
config: {},
name: 'C + 2',
type: 'number',
values: [5, 302],
},
],
length: 2,
},
]);
});
});
@ -479,18 +454,133 @@ describe('calculateField transformer w/ timeseries', () => {
const data = received[0];
const filtered = data[0];
const rows = new DataFrameView(filtered).toArray();
expect(rows).toMatchInlineSnapshot(`
[
{
"E * 1": 1,
"TheTime": 1000,
},
{
"E * 1": 0,
"TheTime": 2000,
},
]
`);
expect(rows).toEqual([
{
'E * 1': 1,
TheTime: 1000,
},
{
'E * 1': 0,
TheTime: 2000,
},
]);
});
});
it('transforms multiple queries + field + field in the old format', async () => {
const cfg = {
id: DataTransformerID.calculateField,
options: {
binary: {
left: 'A',
operator: '+',
reducer: 'sum',
right: 'B',
},
mode: CalculateFieldMode.BinaryOperation,
reduce: {
reducer: 'sum',
},
replaceFields: true,
},
};
await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith((received) => {
const data = received[0];
expect(data).toEqual([
{
fields: [
{
config: {},
name: 'TheTime',
state: {
displayName: 'TheTime',
multipleFrames: false,
},
type: 'time',
values: [1000, 2000],
},
{
config: {},
name: 'A + B',
type: 'number',
values: [3, 300],
},
],
length: 2,
refId: 'joinByField--',
},
]);
});
});
it('transforms multiple queries + field + field in the old format (non existent right field)', async () => {
const cfg = {
id: DataTransformerID.calculateField,
options: {
binary: {
left: 'A',
operator: '+',
reducer: 'sum',
right: 'Z',
},
mode: CalculateFieldMode.BinaryOperation,
reduce: {
reducer: 'sum',
},
replaceFields: true,
},
};
await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith((received) => {
const data = received[0];
expect(data).toEqual([]);
});
});
it('transforms multiple queries + field + number in the old format and does not join', async () => {
const cfg = {
id: DataTransformerID.calculateField,
options: {
binary: {
left: 'A',
operator: '+',
reducer: 'sum',
right: '1336',
},
mode: CalculateFieldMode.BinaryOperation,
reduce: {
reducer: 'sum',
},
replaceFields: true,
},
};
await expect(transformDataFrame([cfg], [seriesA, seriesBC])).toEmitValuesWith((received) => {
const data = received[0];
expect(data).toEqual([
{
fields: [
{
config: {},
name: 'TheTime',
state: {
displayName: 'TheTime',
multipleFrames: true,
},
type: 'time',
values: [1000, 2000],
},
{
config: {},
name: 'A + 1336',
type: 'number',
values: [1337, 1436],
},
],
length: 2,
},
]);
});
});
@ -587,18 +677,16 @@ describe('calculateField transformer w/ timeseries', () => {
const filtered = data[0];
const rows = new DataFrameView(filtered).toArray();
expect(rows).toMatchInlineSnapshot(`
[
{
"Test": 6,
"TheTime": 1000,
},
{
"Test": 105,
"TheTime": 2000,
},
]
`);
expect(rows).toEqual([
{
Test: 6,
TheTime: 1000,
},
{
Test: 105,
TheTime: 2000,
},
]);
});
it('calculates centered moving average on odd window size', async () => {

@ -131,7 +131,10 @@ export const calculateFieldTransformer: DataTransformerInfo<CalculateFieldTransf
const mode = options.mode ?? CalculateFieldMode.ReduceRow;
const asTimeSeries = options.timeSeries !== false;
const isBinaryFixed = mode === CalculateFieldMode.BinaryOperation && options.binary?.right.fixed != null;
const right = options.binary?.right;
const rightVal = typeof right === 'string' ? right : typeof right === 'object' ? right.fixed : undefined;
const isBinaryFixed = mode === CalculateFieldMode.BinaryOperation && !Number.isNaN(Number(rightVal));
const needsSingleFrame = asTimeSeries && !isBinaryFixed;

@ -118,16 +118,17 @@ type TLSConfig struct {
type MqttIntegration struct {
DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"`
BrokerURL *string `json:"brokerUrl,omitempty" yaml:"brokerUrl,omitempty" hcl:"broker_url"`
ClientID *string `json:"clientId,omitempty" yaml:"clientId,omitempty" hcl:"client_id"`
Topic *string `json:"topic,omitempty" yaml:"topic,omitempty" hcl:"topic"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
MessageFormat *string `json:"messageFormat,omitempty" yaml:"messageFormat,omitempty" hcl:"message_format"`
Username *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"username"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"password"`
QoS *int64 `json:"qos,omitempty" yaml:"qos,omitempty" hcl:"qos"`
Retain *bool `json:"retain,omitempty" yaml:"retain,omitempty" hcl:"retain"`
TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tls_config,block"`
BrokerURL *string `json:"brokerUrl,omitempty" yaml:"brokerUrl,omitempty" hcl:"broker_url"`
ClientID *string `json:"clientId,omitempty" yaml:"clientId,omitempty" hcl:"client_id"`
Topic *string `json:"topic,omitempty" yaml:"topic,omitempty" hcl:"topic"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
MessageFormat *string `json:"messageFormat,omitempty" yaml:"messageFormat,omitempty" hcl:"message_format"`
Username *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"username"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"password"`
QoS *int64 `json:"qos,omitempty" yaml:"qos,omitempty" hcl:"qos"`
Retain *bool `json:"retain,omitempty" yaml:"retain,omitempty" hcl:"retain"`
TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tls_config,block"`
AddGroupKeyToTopic *bool `json:"addGroupKeyToTopic,omitempty" yaml:"addGroupKeyToTopic,omitempty" hcl:"add_group_key_to_topic"`
}
type OnCallIntegration struct {

@ -1324,6 +1324,13 @@ func GetAvailableNotifiers() []*NotifierPlugin {
PropertyName: "topic",
Required: true,
},
{
Label: "Add group key to topic",
Element: ElementTypeCheckbox,
Description: "If set, the group key will be appended to the topic.",
PropertyName: "addGroupKeyToTopic",
Required: false,
},
{
Label: "Message format",
Element: ElementTypeSelect,

Loading…
Cancel
Save