mirror of https://github.com/grafana/grafana.git
				
				
				
			Reducers: Add in basic Percentile Support (#85554)
This commit is contained in:
		
							parent
							
								
									5f0ca1429b
								
							
						
					
					
						commit
						c47be316eb
					
				| 
						 | 
				
			
			@ -177,6 +177,9 @@ exports[`better eslint`] = {
 | 
			
		|||
    "packages/grafana-data/src/themes/createColors.ts:5381": [
 | 
			
		||||
      [0, 0, 0, "Do not use any type assertions.", "0"]
 | 
			
		||||
    ],
 | 
			
		||||
    "packages/grafana-data/src/transformations/fieldReducer.ts:5381": [
 | 
			
		||||
      [0, 0, 0, "Do not use any type assertions.", "0"]
 | 
			
		||||
    ],
 | 
			
		||||
    "packages/grafana-data/src/transformations/matchers/valueMatchers/types.ts:5381": [
 | 
			
		||||
      [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
 | 
			
		||||
      [0, 0, 0, "Unexpected any. Specify a different type.", "1"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -251,4 +251,18 @@ describe('Stats Calculators', () => {
 | 
			
		|||
 | 
			
		||||
    expect(reduce(someNulls, ReducerID.count)).toEqual(4);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  for (let i = 1; i < 100; i++) {
 | 
			
		||||
    it(`can reduce the ${i}th percentile`, () => {
 | 
			
		||||
      const preciseStats = reduceField({
 | 
			
		||||
        field: createField(
 | 
			
		||||
          'x',
 | 
			
		||||
          Array.from({ length: 101 }, (_, index) => index)
 | 
			
		||||
        ),
 | 
			
		||||
        reducers: [(ReducerID as Record<string, ReducerID>)[`p${i}`]],
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      expect(preciseStats[`p${i}`]).toEqual(i);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,105 @@ export enum ReducerID {
 | 
			
		|||
  allIsNull = 'allIsNull',
 | 
			
		||||
  allValues = 'allValues',
 | 
			
		||||
  uniqueValues = 'uniqueValues',
 | 
			
		||||
  p1 = 'p1',
 | 
			
		||||
  p2 = 'p2',
 | 
			
		||||
  p3 = 'p3',
 | 
			
		||||
  p4 = 'p4',
 | 
			
		||||
  p5 = 'p5',
 | 
			
		||||
  p6 = 'p6',
 | 
			
		||||
  p7 = 'p7',
 | 
			
		||||
  p8 = 'p8',
 | 
			
		||||
  p9 = 'p9',
 | 
			
		||||
  p10 = 'p10',
 | 
			
		||||
  p11 = 'p11',
 | 
			
		||||
  p12 = 'p12',
 | 
			
		||||
  p13 = 'p13',
 | 
			
		||||
  p14 = 'p14',
 | 
			
		||||
  p15 = 'p15',
 | 
			
		||||
  p16 = 'p16',
 | 
			
		||||
  p17 = 'p17',
 | 
			
		||||
  p18 = 'p18',
 | 
			
		||||
  p19 = 'p19',
 | 
			
		||||
  p20 = 'p20',
 | 
			
		||||
  p21 = 'p21',
 | 
			
		||||
  p22 = 'p22',
 | 
			
		||||
  p23 = 'p23',
 | 
			
		||||
  p24 = 'p24',
 | 
			
		||||
  p25 = 'p25',
 | 
			
		||||
  p26 = 'p26',
 | 
			
		||||
  p27 = 'p27',
 | 
			
		||||
  p28 = 'p28',
 | 
			
		||||
  p29 = 'p29',
 | 
			
		||||
  p30 = 'p30',
 | 
			
		||||
  p31 = 'p31',
 | 
			
		||||
  p32 = 'p32',
 | 
			
		||||
  p33 = 'p33',
 | 
			
		||||
  p34 = 'p34',
 | 
			
		||||
  p35 = 'p35',
 | 
			
		||||
  p36 = 'p36',
 | 
			
		||||
  p37 = 'p37',
 | 
			
		||||
  p38 = 'p38',
 | 
			
		||||
  p39 = 'p39',
 | 
			
		||||
  p40 = 'p40',
 | 
			
		||||
  p41 = 'p41',
 | 
			
		||||
  p42 = 'p42',
 | 
			
		||||
  p43 = 'p43',
 | 
			
		||||
  p44 = 'p44',
 | 
			
		||||
  p45 = 'p45',
 | 
			
		||||
  p46 = 'p46',
 | 
			
		||||
  p47 = 'p47',
 | 
			
		||||
  p48 = 'p48',
 | 
			
		||||
  p49 = 'p49',
 | 
			
		||||
  p50 = 'p50',
 | 
			
		||||
  p51 = 'p51',
 | 
			
		||||
  p52 = 'p52',
 | 
			
		||||
  p53 = 'p53',
 | 
			
		||||
  p54 = 'p54',
 | 
			
		||||
  p55 = 'p55',
 | 
			
		||||
  p56 = 'p56',
 | 
			
		||||
  p57 = 'p57',
 | 
			
		||||
  p58 = 'p58',
 | 
			
		||||
  p59 = 'p59',
 | 
			
		||||
  p60 = 'p60',
 | 
			
		||||
  p61 = 'p61',
 | 
			
		||||
  p62 = 'p62',
 | 
			
		||||
  p63 = 'p63',
 | 
			
		||||
  p64 = 'p64',
 | 
			
		||||
  p65 = 'p65',
 | 
			
		||||
  p66 = 'p66',
 | 
			
		||||
  p67 = 'p67',
 | 
			
		||||
  p68 = 'p68',
 | 
			
		||||
  p69 = 'p69',
 | 
			
		||||
  p70 = 'p70',
 | 
			
		||||
  p71 = 'p71',
 | 
			
		||||
  p72 = 'p72',
 | 
			
		||||
  p73 = 'p73',
 | 
			
		||||
  p74 = 'p74',
 | 
			
		||||
  p75 = 'p75',
 | 
			
		||||
  p76 = 'p76',
 | 
			
		||||
  p77 = 'p77',
 | 
			
		||||
  p78 = 'p78',
 | 
			
		||||
  p79 = 'p79',
 | 
			
		||||
  p80 = 'p80',
 | 
			
		||||
  p81 = 'p81',
 | 
			
		||||
  p82 = 'p82',
 | 
			
		||||
  p83 = 'p83',
 | 
			
		||||
  p84 = 'p84',
 | 
			
		||||
  p85 = 'p85',
 | 
			
		||||
  p86 = 'p86',
 | 
			
		||||
  p87 = 'p87',
 | 
			
		||||
  p88 = 'p88',
 | 
			
		||||
  p89 = 'p89',
 | 
			
		||||
  p90 = 'p90',
 | 
			
		||||
  p91 = 'p91',
 | 
			
		||||
  p92 = 'p92',
 | 
			
		||||
  p93 = 'p93',
 | 
			
		||||
  p94 = 'p94',
 | 
			
		||||
  p95 = 'p95',
 | 
			
		||||
  p96 = 'p96',
 | 
			
		||||
  p97 = 'p97',
 | 
			
		||||
  p98 = 'p98',
 | 
			
		||||
  p99 = 'p99',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isReducerID(id: string): id is ReducerID {
 | 
			
		||||
| 
						 | 
				
			
			@ -305,6 +404,26 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
 | 
			
		|||
  },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
for (let i = 1; i < 100; i++) {
 | 
			
		||||
  const percentile = i / 100;
 | 
			
		||||
  const id = `p${i}` as ReducerID;
 | 
			
		||||
  const nth = (n: number) =>
 | 
			
		||||
    n > 3 && n < 21 ? 'th' : n % 10 === 1 ? 'st' : n % 10 === 2 ? 'nd' : n % 10 === 3 ? 'rd' : 'th';
 | 
			
		||||
  const name = `${i}${nth(i)} percentile`;
 | 
			
		||||
  const description = `${i}${nth(i)} percentile value`;
 | 
			
		||||
 | 
			
		||||
  fieldReducers.register({
 | 
			
		||||
    id: id,
 | 
			
		||||
    name: name,
 | 
			
		||||
    description: description,
 | 
			
		||||
    standard: false,
 | 
			
		||||
    reduce: (field: Field, ignoreNulls: boolean, nullAsZero: boolean): FieldCalcs => {
 | 
			
		||||
      return { [id]: calculatePercentile(field, percentile, ignoreNulls, nullAsZero) };
 | 
			
		||||
    },
 | 
			
		||||
    preservesUnits: true,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Used for test cases
 | 
			
		||||
export const defaultCalcs: FieldCalcs = {
 | 
			
		||||
  sum: 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -325,7 +444,6 @@ export const defaultCalcs: FieldCalcs = {
 | 
			
		|||
  delta: 0,
 | 
			
		||||
  step: Number.MAX_VALUE,
 | 
			
		||||
  diffperc: 0,
 | 
			
		||||
 | 
			
		||||
  // Just used for calculations -- not exposed as a stat
 | 
			
		||||
  previousDeltaUp: true,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -553,3 +671,18 @@ function calculateDistinctCount(field: Field, ignoreNulls: boolean, nullAsZero:
 | 
			
		|||
  }
 | 
			
		||||
  return { distinctCount: distinct.size };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function calculatePercentile(field: Field, percentile: number, ignoreNulls: boolean, nullAsZero: boolean): number {
 | 
			
		||||
  let data = field.values;
 | 
			
		||||
 | 
			
		||||
  if (ignoreNulls) {
 | 
			
		||||
    data = data.filter((value) => value !== null);
 | 
			
		||||
  }
 | 
			
		||||
  if (nullAsZero) {
 | 
			
		||||
    data = data.map((value) => (value === null ? 0 : value));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const sorted = data.slice().sort((a, b) => a - b);
 | 
			
		||||
  const index = Math.round((sorted.length - 1) * percentile);
 | 
			
		||||
  return sorted[index];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue