Ok - so I think this should be done with a Transform, which will pivot
on the test name and then use a scripted_metric
aggregation to gather up all of the individual test runs, sort them by time, then count the number of times the test result "flips state" (from "pass to fail" or "fail to pass").
Here's an example based on the other examples here:
Note: this uses the _preview
endpoint of the transform, but you can have the Transform persist the results to an index to use for reporting, etc.
First, I define an index to use
PUT testruns/
{
"mappings": {
"properties": {
"testrundate": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"testnamne": {
"type": "keyword"
},
"result": {
"type": "keyword"
}
}
}
}
Next, I put some sample docs in there:
PUT testruns/_doc/1
{
"testrundate": "2022-05-20 12:00:00",
"testnamne": "foo",
"result": "pass"
}
PUT testruns/_doc/2
{
"testrundate": "2022-05-20 13:00:00",
"testnamne": "foo",
"result": "fail"
}
PUT testruns/_doc/3
{
"testrundate": "2022-05-20 14:00:00",
"testnamne": "foo",
"result": "pass"
}
PUT testruns/_doc/4
{
"testrundate": "2022-05-20 13:00:00",
"testnamne": "bar",
"result": "fail"
}
PUT testruns/_doc/5
{
"testrundate": "2022-05-20 14:00:00",
"testnamne": "bar",
"result": "fail"
}
PUT testruns/_doc/6
{
"testrundate": "2022-05-20 11:00:00",
"testnamne": "foo",
"result": "fail"
}
Now I run the Transform:
POST _transform/_preview
{
"source": {
"index": [
"testruns"
]
},
"pivot": {
"group_by": {
"testnamne": {
"terms": {
"field": "testnamne"
}
}
},
"aggregations": {
"num_results": {
"value_count": {
"field": "result"
}
},
"flipflops" :
{
"scripted_metric": {
"init_script": "state.docs = []",
"map_script": """
Map testrun = [
'testrundate':doc['testrundate'].value,
'testnamne':doc['testnamne'].value,
'result':doc['result'].value
];
state.docs.add(testrun)
""",
"combine_script": "return state.docs;",
"reduce_script": """
def all_docs = [];
for (s in states) {
for (testrun in s) {
all_docs.add(testrun);
}
}
all_docs.sort((HashMap o1, HashMap o2)->o1['testrundate'].toEpochMilli().compareTo(o2['testrundate'].toEpochMilli()));
def size = all_docs.size();
def min_time = all_docs[0]['testrundate'];
def max_time = all_docs[size-1]['testrundate'];
def duration = max_time.toEpochMilli() - min_time.toEpochMilli();
def last_testresult = '';
def flipflopcount = 0L;
for (s in all_docs) {if (s.result != (last_testresult)) {flipflopcount++; last_testresult = s.result;}}
def ret = new HashMap();
ret['first_time'] = min_time;
ret['last_time'] = max_time;
ret['duration_minutes'] = duration/1000/60;
ret['flipflopcount'] = flipflopcount-1;
return ret;
"""
}
}
}
}
}
And the output is:
"preview" : [
{
"testnamne" : "bar",
"num_results" : 2,
"flipflops" : {
"duration_minutes" : 60,
"first_time" : "2022-05-20T13:00:00.000Z",
"last_time" : "2022-05-20T14:00:00.000Z",
"flipflopcount" : 0
}
},
{
"testnamne" : "foo",
"num_results" : 4,
"flipflops" : {
"duration_minutes" : 180,
"first_time" : "2022-05-20T11:00:00.000Z",
"last_time" : "2022-05-20T14:00:00.000Z",
"flipflopcount" : 3
}
}
],
This makes sense as test foo
had 4 runs and transitioned 3 times and test bar
ran twice and never transitioned (it was always failing)