I have an Azure AlertRule what validates a query, where to tables are joining on a timestamp.
Is seems like Azure is changing the query, replacing the statement bin(..) with bin_at(..).
The Original query:
The query, opened after the alert triggered.
In my case this change is enough to alter the result of the query, becuase the extra added parameter (yellow arrow).
Is there any way around the issue?
EDIT:
as David pointed out in the comments -
This behaviour can be reproduced by running this kql as the query of an alart rule:
print bin(now(), 1h)
Original:
For completness, i've added the entier query here. I was not able to shorten it more that this. (sorry)
let frame_size = 1h;
let messages = datatable (timestamp: datetime )
[
datetime(2022-11-09T23:01:00Z),datetime(2022-11-09T23:02:00Z),datetime(2022-11-09T23:03:00Z),datetime(2022-11-09T23:04:00Z),
datetime(2022-11-09T22:01:00Z),datetime(2022-11-09T22:02:00Z),datetime(2022-11-09T22:03:00Z),datetime(2022-11-09T22:04:00Z),
datetime(2022-11-09T21:01:00Z),datetime(2022-11-09T21:02:00Z),datetime(2022-11-09T21:03:00Z),datetime(2022-11-09T21:04:00Z),
datetime(2022-11-09T20:01:00Z),datetime(2022-11-09T20:02:00Z),datetime(2022-11-09T20:03:00Z),datetime(2022-11-09T20:04:00Z),
datetime(2022-11-09T19:01:00Z),datetime(2022-11-09T19:02:00Z),datetime(2022-11-09T19:03:00Z),datetime(2022-11-09T19:04:00Z),
datetime(2022-11-09T18:01:00Z),datetime(2022-11-09T18:02:00Z),datetime(2022-11-09T18:03:00Z),datetime(2022-11-09T18:04:00Z),
datetime(2022-11-09T17:01:00Z),datetime(2022-11-09T17:02:00Z),datetime(2022-11-09T17:03:00Z),datetime(2022-11-09T17:04:00Z),
datetime(2022-11-09T16:01:00Z),datetime(2022-11-09T16:02:00Z),datetime(2022-11-09T16:03:00Z),datetime(2022-11-09T16:04:00Z),
datetime(2022-11-09T15:01:00Z),datetime(2022-11-09T15:02:00Z),datetime(2022-11-09T15:03:00Z),datetime(2022-11-09T15:04:00Z),
datetime(2022-11-09T14:01:00Z),datetime(2022-11-09T14:02:00Z),
datetime(2022-11-09T13:01:00Z),datetime(2022-11-09T13:02:00Z),
datetime(2022-11-09T12:01:00Z),datetime(2022-11-09T12:02:00Z),
datetime(2022-11-09T11:01:00Z),datetime(2022-11-09T11:02:00Z),
datetime(2022-11-09T10:01:00Z),datetime(2022-11-09T10:02:00Z),
datetime(2022-11-09T09:01:00Z),datetime(2022-11-09T09:02:00Z),
datetime(2022-11-09T08:01:00Z),datetime(2022-11-09T08:02:00Z),
datetime(2022-11-09T07:01:00Z),datetime(2022-11-09T07:02:00Z),
datetime(2022-11-09T06:01:00Z),datetime(2022-11-09T06:02:00Z),
datetime(2022-11-09T06:01:00Z),datetime(2022-11-09T06:02:00Z),
datetime(2022-11-09T05:01:00Z),datetime(2022-11-09T05:02:00Z),
datetime(2022-11-09T04:01:00Z),datetime(2022-11-09T04:02:00Z),
datetime(2022-11-09T03:01:00Z),datetime(2022-11-09T03:02:00Z),
datetime(2022-11-09T02:01:00Z),datetime(2022-11-09T02:02:00Z),
datetime(2022-11-09T01:01:00Z),datetime(2022-11-09T01:02:00Z),
datetime(2022-11-09T00:01:00Z),datetime(2022-11-09T00:02:00Z),
];
let create_time_intervals = (start_datetime: datetime, end_datetime: datetime, frame_size: timespan)
{
let hourly_distribution_as_dk_time = datatable (hour: int, expected_documents_count: int)
[0,2, 1,2, 2,2, 3,2, 4,2, 5,2, 6,2, 7,2, 8,2, 9,2, 10,2, 11,2, 12,2, 13,2, 14,2, 15,4, 16,4, 17,4, 18,4, 19,4, 20,4, 21,4, 22,4, 23,4 ]
;
let start_datetime_dk = datetime_utc_to_local(start_datetime, "Europe/Copenhagen");
let end_datetime_dk = datetime_utc_to_local(end_datetime, "Europe/Copenhagen");
// Generate all time frames for given interval excluding the first incomplete frame.
range frame_start from bin(start_datetime + frame_size, frame_size) to end_datetime step frame_size
| project bin(frame_start, frame_size)
| extend date_dk = datetime_utc_to_local(frame_start, "Europe/Copenhagen")
| extend hour = toint(datetime_part("Hour", date_dk))
| join kind=leftouter hourly_distribution_as_dk_time on hour
| project frame_start, expected_documents_count
| order by frame_start asc
};
let msg = messages
| where timestamp >= ago(1h)
|summarize cnt = count()
by frame_start = bin(timestamp,frame_size); // *** <==== this bin(..) changes when running ***
let frame_results = create_time_intervals(ago(1h), now(), frame_size)
| join kind=leftouter msg on frame_start
| extend ok = cnt;
frame_results
| summarize
sum_expected = sum(expected_documents_count),
sum_ok = sum(ok)
| extend ok = sum_expected == sum_ok
| extend ok_int = toint(ok) //for alerting purposes
Well, an obvious work-around would be to replace bin with bin_at, e.g. -
let timestamp = datetime(2000-03-04 11:22:33);
let frame_size = 1h;
print bin_at(timestamp, frame_size, datetime(2000))
print_0
2000-03-04T11:00:00Z
Fiddle
I have a query from which I want to get a few variables: street, house, locality, region, countryName
let orderDate = datetime('2022-02-10');
let orderId = '0A524A2F83CCB1A311EC827458D6C4F6';
raw_events
| where timestamp between (orderDate .. 1d)
| where messageType == 'OrderPlacedEvent'
| extend messageAsJSON=parse_json(message)
| where messageAsJSON.orderId == orderId
| extend street = messageAsJSON.address.street
| extend house = messageAsJSON.address.house
| extend locality = messageAsJSON.address.locality
| extend region = messageAsJSON.address.region
| extend countryName = messageAsJSON.address.countryName
Is there a better way of achieving that other than executing the same query a few times:
let orderDate = datetime('2022-02-10');
let orderId = '0A524A2F83CCB1A311EC827458D6C4F6';
let street = raw_events
| where timestamp between (orderDate .. 1d)
| where messageType == 'OrderPlacedEvent'
| extend messageAsJSON=parse_json(message)
| where messageAsJSON.orderId == orderId
| project street = messageAsJSON.address.street;
let house = raw_events
| where timestamp between (orderDate .. 1d)
| where messageType == 'OrderPlacedEvent'
| extend messageAsJSON=parse_json(message)
| where messageAsJSON.orderId == orderId
| project street = messageAsJSON.address.house;
print toscalar(street),toscalar(house);
UPDATE
Made it the following way
let addres2look4 = raw_events
| where timestamp between (orderDate .. 1d)
| where messageType == mType
| where message.orderId == orderId
| project address = message.address
| evaluate bag_unpack(address);
let countryName = toscalar(addres2look4 | project countryName);
let region = toscalar(addres2look4 | project region);
let locality = toscalar(addres2look4 | project locality);
let street = toscalar(addres2look4 | project street);
let house = toscalar(addres2look4 | project house);
Here's an efficient way to achieve this (note the use of the materialize() function):
let addres2look4 = materialize(
raw_events
| where timestamp between (orderDate .. 1d)
| where messageType == mType
| where message.orderId == orderId
| project address = message.address
| evaluate bag_unpack(address));
let countryName = toscalar(addres2look4 | project countryName);
let region = toscalar(addres2look4 | project region);
let locality = toscalar(addres2look4 | project locality);
let street = toscalar(addres2look4 | project street);
let house = toscalar(addres2look4 | project house);
The materialize() function caches the subquery's result during the time of query execution, so that the subsequent let statements will be much faster.
Jest provides the test.each function, that allows nice testing of a table driven test input matrix via tagged templates. Is there an equivalent function / plugin available for Chai? I did search through the Chai Plugins but could not find something similar.
Actually this could be also solved with a generic solution, regardless of the used assertion / test runner, however the only ones (Sazerac, Mocha Table, Chai Things) I've found are either using fluent APIs or work on arrays, whereas I like to have them basically work with super-charged markdown tables:
const testInput =
`| enableMixed | oldValue | newValue | emitChange |
| ----------- | -------- | -------- | ---------- |
| ${false} | ${false} | ${false} | ${false} |
| ${false} | ${false} | ${true} | ${true} |
| ${false} | ${false} | ${'mixed'} | ${false} |
`
Okay so it seems, there is nothing really out there and I came up with my own cooked solution.
Generating tests dynamically as in Jest is in Mocha / Chai pretty simple and with the following helper function:
/**
*
* This is a helper function, that parses a markdown table via a tagged template literal
* and returns a function needing a callback to be passed; with which it is invoked
* for each row of the table with the values filled in with the columnHeaderName
*
* #param tableMarkup
* #param substitutions
*/
export function callForEachRow(tableMarkup: TemplateStringsArray, ...substitutions: any[]): (callback: Function) => void {
const headerMarkup = tableMarkup[0];
// filter all table headers by splitting with '|' on initial and removing everything empty after having stuff trimmed
const keys = headerMarkup.split('|')
.map(cellContent => cellContent.trim())
.filter(trimmedContent => trimmedContent !== '')
.filter(trimmedContent => trimmedContent !== '\n')
.filter(trimmedContent => !trimmedContent.startsWith('-'));
// determine rowCount and offset index by rowCount
const rowCount = substitutions.length / keys.length;
const columnCount = keys.length;
return (callback) => {
// invoke callback for each row
const rows = Array.from({ length: rowCount });
let returnPromise = false;
// the result of each callback might either be a promise or not
// if it is promise, wait until it resolves
return rows.reduce((acc, current, row) => {
// create obj for current row
const obj = keys.reduce((acc, key, index) => {
// and shift through substitutions by rowOffset
acc[key] = substitutions[index + row * columnCount];
return acc;
}, {});
// determine, whether we need to return a promise...
const result = callback(obj, row);
if (row === 0 && result instanceof Promise) {
returnPromise = true;
acc = result;
}
// if we are to return a promise, we need to sequentially execute them
// by deferring their call to then handler of the previous one
if (returnPromise) {
// #ts-ignore because TS does not know, that we are dynamically changing the type
return acc.then(() => {
return result
});
} else {
return acc;
}
}, undefined);
};
}
I can either generate tests dynamically (as the test.each) helper does:
callForEachRow`
| enableMixed | value | newValue |
| ------------ | ---------- | ---------- |
| ${false} | ${false} | ${false} |
| ${false} | ${false} | ${'mixed'} |
`(({ enableMixed, value, newValue }) => {
it(`should not be dispatched if the value changes from ${value} to ${newValue} with enableMixed set to ${enableMixed} `, async () => {
// ...
});
});
or simply generate expectations. The passed callback can either be async or sync. If it is async, each callback will be called after the previous resolved.
This allows to have descriptive data-driven tests, instead of manually writing a lot of test cases and possible forgetting some edge cases.
I have the following very streamlined version of a spreadsheet:
Sect | Lbl | A | B | C | D | E
==========================================================
Sec1 | Lbl1 | 1 | 8 | 6 | 10 |
----------------------------------------------------------
Sec2 | Lbl2 | 2 | 1 | 1 | >100 |
----------------------------------------------------------
etc...
I want to apply a rule/rules across all values to say:
Bg Color = Green if:
- the cell to the right is not blank and higher than this value
Bg Color = Red if:
- the cell to the right is not blank and less than this value
Bg Color = white (no action) if:
- the cell to the right has the same value
In addition, if the value is set to the non-numeric ">100" I need to convert it to 100 as part of this formatting.
I'm using C# to do this with the Spreadsheets v4 API.
So far I have the code below but I'm unsure as to how I could apply multiple conditions to a formatting rule.
Update
Please note updated table example above before reading below
Thanks to TheMaster I've got something up and running but it's not quite right yet. I've got an additional factor of:
the first row should be excluded from the conditional formatting
the first two columns are labels and should also be ignored/excluded
all other columns and rows of data need to be compared to the column to their right (if available) and coloured accordingly.
Here is my code so far for the Red rule (where cell value > cell value to the right).
In addition to this I have a Green rule (where cell value < cell value to the right) and a White rule (where cell value = cell value to the right)
They are indexed as below in a batchupdate request:
0 = red
1 = green
2 = white
The Code for the Red rule:
formatRequest.Requests.Add(new Google.Apis.Sheets.v4.Data.Request()
{
AddConditionalFormatRule = new AddConditionalFormatRuleRequest()
{
Rule = new ConditionalFormatRule()
{
BooleanRule = new BooleanRule()
{
Condition = new BooleanCondition()
{
Type = "CUSTOM_FORMULA",
Values = new List<ConditionValue>() {
new ConditionValue()
{
UserEnteredValue = "=AND(NOT(ISBLANK(A2)),(1*REGEXEXTRACT(A2,\"\\d+\"))>(1*REGEXEXTRACT(B2,\"\\d+\")))"
}
}
},
Format = new CellFormat()
{
BackgroundColor = new Color()
{
Red = 0.8f,
Green = 0f,
Blue = 0f,
Alpha = 1f
}
}
},
Ranges = new List<GridRange>()
{
new GridRange()
{
SheetId = Convert.ToInt32(sheetId)
,StartRowIndex = 1
},
}
},
Index = 0
}
});
The problem is it doesn't apply the conditional formatting to the whole sheet...only the first column of data.
You should use CUSTOM_FORMULA as Boolean condition Type
You'll need to add two conditional formatting rules with index 0 and 1
Range will be open-ended and cover the full sheet.
Snippet(for A1:Z;Bg:red):
Boolean Condition JSON:
{
"type": "CUSTOM_FORMULA",
"values": [
{
userEnteredValue: "=AND(NOT(ISBLANK(A1)),A1>(IF(ISNUMBER(B1),B1,1*REGEXEXTRACT(B1,\"\d+\"))))"
}
]
}
BooleanRule/Format/BackgroundColor JSON:
{
"red": 1,
"green": 0,
"blue": 0,
"alpha": 1
}
I have a table with two columns, and I want to calculate the third.
Column 1 | Column 2 | Calculated Column
1 | 1 | 1
1 | 1.5 | 2
1 | 2 | 3
2 | 2 | 1
I am trying to add the third column to my sheet and then in ascending order increment the calculate value.. Once the value in column 1 changes, the incrementing should start over. I was good at this once.. but rusty :) Any help would be awesome
//name the datasets
var ss=SpreadsheetApp.getActiveSpreadsheet();
//add a new column and name it
ss.insertColumnAfter(2);
ss.getRange("C1").setValue("Tier");
//Get the first store number and result space number
var atvStr = ss.getRange("A2").getValue();
var strNbrR = ss.getRange("A2:A9000").getValues();
var rslt = ss.getRange("C2:C9000").getValues();
//get the last filled row and +1 to go on to the next
var line=ss.getLastRow()+1;
for (i in strNbrR) {
if (strNbrR[i] = atvStr) {
ss.getRange(line).setValue("1");
}
}
}
If I understand you correctly, try this:
function testMe(){
//name the datasets
var ss=SpreadsheetApp.getActiveSpreadsheet();
//add a new column and name it
ss.insertColumnAfter(2);
//Get the first store number and result space number
var strNbrR = ss.getRange("A2:A").getValues();
var rsltRng = ss.getRange("C2:C");
var rsltVals = rsltRng.getValues();
var newValue = 1;
for ( i in strNbrR) {
if (i >1 ){
if (strNbrR[i][0] !== strNbrR[i - 1][0]) {
++newValue;
}
}
rsltVals[i][0] = newValue;
}
rsltRng.setValues(rsltVals);
}
It will increment the value placed in Column C if the value in A is not the same as the value in cell A in the previous row.