Skip to content

Commit 47a0ae0

Browse files
authored
feat: adding topology custom metric model and service (#999)
* feat: adding topology custom metric model and service * refactor: adding test
1 parent 7d48c89 commit 47a0ae0

9 files changed

+604
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Color } from '@hypertrace/common';
2+
import { createModelFactory } from '@hypertrace/dashboards/testing';
3+
import { TopologyMetricCategoryModel } from './topology-metric-category.model';
4+
5+
describe('Topology Metric with category model', () => {
6+
const modelFactory = createModelFactory();
7+
8+
test('provides category name correctly', () => {
9+
const spectator = modelFactory(TopologyMetricCategoryModel, {
10+
properties: {
11+
name: 'test name',
12+
minValue: 0,
13+
maxValue: 10,
14+
fillColor: Color.Blue2,
15+
strokeColor: Color.Blue3,
16+
focusColor: Color.Blue4
17+
}
18+
});
19+
20+
expect(spectator.model.getCategoryClassName()).toContain('test-name');
21+
});
22+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Color } from '@hypertrace/common';
2+
import { BOOLEAN_PROPERTY, Model, ModelProperty, NUMBER_PROPERTY, STRING_PROPERTY } from '@hypertrace/hyperdash';
3+
import { kebabCase, uniqueId } from 'lodash-es';
4+
5+
@Model({
6+
type: 'topology-metric-category'
7+
})
8+
export class TopologyMetricCategoryModel implements TopologyMetricCategoryData {
9+
@ModelProperty({
10+
key: 'name',
11+
required: true,
12+
type: STRING_PROPERTY.type
13+
})
14+
public name!: string;
15+
16+
@ModelProperty({
17+
key: 'minValue',
18+
required: true,
19+
type: NUMBER_PROPERTY.type
20+
})
21+
public minValue!: number;
22+
23+
@ModelProperty({
24+
key: 'maxValue',
25+
required: false,
26+
type: NUMBER_PROPERTY.type
27+
})
28+
public maxValue?: number;
29+
30+
@ModelProperty({
31+
key: 'fillColor',
32+
required: true,
33+
type: STRING_PROPERTY.type
34+
})
35+
public fillColor!: Color;
36+
37+
@ModelProperty({
38+
key: 'strokeColor',
39+
required: true,
40+
type: STRING_PROPERTY.type
41+
})
42+
public strokeColor!: Color;
43+
44+
@ModelProperty({
45+
key: 'focusColor',
46+
required: true,
47+
type: STRING_PROPERTY.type
48+
})
49+
public focusColor!: Color;
50+
51+
@ModelProperty({
52+
key: 'highestPrecedence',
53+
required: false,
54+
type: BOOLEAN_PROPERTY.type
55+
})
56+
public highestPrecedence?: boolean = false;
57+
58+
private readonly id: string = uniqueId();
59+
60+
public getCategoryClassName(): string {
61+
return `${kebabCase(this.name)}-${this.id}`;
62+
}
63+
}
64+
65+
export interface TopologyMetricCategoryData {
66+
name: string;
67+
minValue: number;
68+
maxValue?: number;
69+
fillColor: string;
70+
strokeColor: string;
71+
focusColor: string;
72+
highestPrecedence?: boolean;
73+
getCategoryClassName(): string;
74+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Color } from '@hypertrace/common';
2+
import { createModelFactory } from '@hypertrace/dashboards/testing';
3+
import { MetricAggregationType } from '@hypertrace/distributed-tracing';
4+
import { MetricAggregationSpecificationModel } from '../../specifiers/metric-aggregation-specification.model';
5+
import { TopologyMetricCategoryModel } from './topology-metric-category.model';
6+
import { TopologyMetricWithCategoryModel } from './topology-metric-with-category.model';
7+
8+
describe('Topology Metric with category model', () => {
9+
const modelFactory = createModelFactory();
10+
11+
const createCategoryModel = (
12+
name: string,
13+
minValue: number,
14+
fillColor: Color,
15+
strokeColor: Color,
16+
focusColor: Color,
17+
maxValue?: number
18+
): TopologyMetricCategoryModel => {
19+
const categoryModel = new TopologyMetricCategoryModel();
20+
categoryModel.name = name;
21+
categoryModel.minValue = minValue;
22+
categoryModel.maxValue = maxValue;
23+
categoryModel.fillColor = fillColor;
24+
categoryModel.strokeColor = strokeColor;
25+
categoryModel.focusColor = focusColor;
26+
27+
return categoryModel;
28+
};
29+
test('provides category name correctly', () => {
30+
const specification = new MetricAggregationSpecificationModel();
31+
specification.metric = 'metric-name';
32+
specification.aggregation = MetricAggregationType.Average;
33+
specification.modelOnInit();
34+
35+
const categories = [
36+
createCategoryModel('first', 0, Color.Blue2, Color.Blue3, Color.Blue4, 10),
37+
createCategoryModel('second', 10, Color.Red1, Color.Red3, Color.Red4, 50),
38+
createCategoryModel('third', 50, Color.Blue2, Color.Blue3, Color.Blue4)
39+
];
40+
41+
const spectator = modelFactory(TopologyMetricWithCategoryModel, {
42+
properties: {
43+
specification: specification,
44+
categories: categories
45+
}
46+
});
47+
48+
expect(
49+
spectator.model.extractAndGetDataCategoryForMetric({
50+
[specification.resultAlias()]: { value: 50 }
51+
})
52+
).toEqual(categories[2]);
53+
54+
expect(
55+
spectator.model.extractAndGetDataCategoryForMetric({
56+
[specification.resultAlias()]: { value: 5 }
57+
})
58+
).toEqual(categories[0]);
59+
60+
expect(
61+
spectator.model.extractAndGetDataCategoryForMetric({
62+
[specification.resultAlias()]: { value: 22 }
63+
})
64+
).toEqual(categories[1]);
65+
66+
expect(
67+
spectator.model.extractAndGetDataCategoryForMetric({
68+
[specification.resultAlias()]: { value: -10 }
69+
})
70+
).toEqual(undefined);
71+
});
72+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Dictionary } from '@hypertrace/common';
2+
import { MetricAggregation } from '@hypertrace/distributed-tracing';
3+
import {
4+
ARRAY_PROPERTY,
5+
Model,
6+
ModelModelPropertyTypeInstance,
7+
ModelProperty,
8+
ModelPropertyType
9+
} from '@hypertrace/hyperdash';
10+
import { MetricAggregationSpecificationModel } from '../../specifiers/metric-aggregation-specification.model';
11+
import { MetricAggregationSpecification } from './../../../../../graphql/model/schema/specifications/metric-aggregation-specification';
12+
import { TopologyMetricCategoryData, TopologyMetricCategoryModel } from './topology-metric-category.model';
13+
14+
@Model({
15+
type: 'topology-metric-with-category'
16+
})
17+
export class TopologyMetricWithCategoryModel implements TopologyMetricWithCategoryData {
18+
@ModelProperty({
19+
key: 'specification',
20+
required: true,
21+
// tslint:disable-next-line: no-object-literal-type-assertion
22+
type: {
23+
key: ModelPropertyType.TYPE,
24+
defaultModelClass: MetricAggregationSpecificationModel
25+
} as ModelModelPropertyTypeInstance
26+
})
27+
public specification!: MetricAggregationSpecificationModel;
28+
29+
@ModelProperty({
30+
key: 'categories',
31+
required: false,
32+
type: ARRAY_PROPERTY.type
33+
})
34+
public categories: TopologyMetricCategoryModel[] = [];
35+
36+
public extractAndGetDataCategoryForMetric(data: Dictionary<unknown>): TopologyMetricCategoryData | undefined {
37+
const aggregation = this.extractDataForMetric(data);
38+
39+
return aggregation !== undefined ? this.getDataCategoryForMetric(aggregation.value) : undefined;
40+
}
41+
42+
public extractDataForMetric(data: Dictionary<unknown>): MetricAggregation | undefined {
43+
return data[this.specification.resultAlias()] as MetricAggregation | undefined;
44+
}
45+
46+
private getDataCategoryForMetric(value: number): TopologyMetricCategoryData | undefined {
47+
return this.categories.find(
48+
category => value >= category.minValue && (category.maxValue !== undefined ? value < category.maxValue : true)
49+
);
50+
}
51+
}
52+
53+
export interface TopologyMetricWithCategoryData {
54+
specification: MetricAggregationSpecification;
55+
categories: TopologyMetricCategoryData[];
56+
extractDataForMetric(data: Dictionary<unknown>): MetricAggregation | undefined;
57+
extractAndGetDataCategoryForMetric(data: Dictionary<unknown>): TopologyMetricCategoryData | undefined;
58+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {
2+
ARRAY_PROPERTY,
3+
Model,
4+
ModelModelPropertyTypeInstance,
5+
ModelProperty,
6+
ModelPropertyType
7+
} from '@hypertrace/hyperdash';
8+
import { TopologyMetricWithCategoryData, TopologyMetricWithCategoryModel } from './topology-metric-with-category.model';
9+
10+
@Model({
11+
type: 'topology-metrics'
12+
})
13+
export class TopologyMetricsModel implements TopologyMetricsData {
14+
@ModelProperty({
15+
key: 'primary',
16+
required: true,
17+
// tslint:disable-next-line: no-object-literal-type-assertion
18+
type: {
19+
key: ModelPropertyType.TYPE,
20+
defaultModelClass: TopologyMetricWithCategoryModel
21+
} as ModelModelPropertyTypeInstance
22+
})
23+
public primary!: TopologyMetricWithCategoryModel;
24+
25+
@ModelProperty({
26+
key: 'secondary',
27+
required: false,
28+
// tslint:disable-next-line: no-object-literal-type-assertion
29+
type: {
30+
key: ModelPropertyType.TYPE,
31+
defaultModelClass: TopologyMetricWithCategoryModel
32+
} as ModelModelPropertyTypeInstance
33+
})
34+
public secondary?: TopologyMetricWithCategoryModel;
35+
36+
@ModelProperty({
37+
key: 'others',
38+
required: false,
39+
type: ARRAY_PROPERTY.type
40+
})
41+
public others?: TopologyMetricWithCategoryModel[];
42+
}
43+
44+
export interface TopologyMetricsData {
45+
primary: TopologyMetricWithCategoryData;
46+
secondary?: TopologyMetricWithCategoryData;
47+
others?: TopologyMetricWithCategoryData[];
48+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Color } from '@hypertrace/common';
2+
import { TopologyMetricCategoryData } from '../../../data/graphql/topology/metrics/topology-metric-category.model';
3+
4+
const enum PrimaryEdgeMetricCategoryValueType {
5+
LessThan20 = 'less-than-20',
6+
From20To100 = 'from-20-to-100',
7+
From100To500 = 'from-100-to-500',
8+
From500To1000 = 'from-500-to-1000',
9+
GreaterThanOrEqualTo1000 = 'greater-than-or-equal-to-1000',
10+
NotSpecified = 'not-specified'
11+
}
12+
13+
const enum SecondaryEdgeMetricCategoryValueType {
14+
LessThan5 = 'less-than-5',
15+
GreaterThanOrEqualTo5 = 'greater-than-or-equal-to-5',
16+
NotSpecified = 'not-specified'
17+
}
18+
19+
export const defaultPrimaryEdgeMetricCategories: Omit<TopologyMetricCategoryData, 'getCategoryClassName'>[] = [
20+
{
21+
name: PrimaryEdgeMetricCategoryValueType.LessThan20,
22+
minValue: 0,
23+
maxValue: 20,
24+
fillColor: Color.BlueGray1,
25+
strokeColor: Color.BlueGray1,
26+
focusColor: Color.BlueGray1
27+
},
28+
{
29+
name: PrimaryEdgeMetricCategoryValueType.From20To100,
30+
minValue: 20,
31+
maxValue: 100,
32+
fillColor: Color.BlueGray2,
33+
strokeColor: Color.BlueGray2,
34+
focusColor: Color.BlueGray2
35+
},
36+
{
37+
name: PrimaryEdgeMetricCategoryValueType.From100To500,
38+
minValue: 100,
39+
maxValue: 500,
40+
fillColor: Color.BlueGray3,
41+
strokeColor: Color.BlueGray3,
42+
focusColor: Color.BlueGray3
43+
},
44+
{
45+
name: PrimaryEdgeMetricCategoryValueType.From500To1000,
46+
minValue: 500,
47+
maxValue: 1000,
48+
fillColor: Color.BlueGray4,
49+
strokeColor: Color.BlueGray4,
50+
focusColor: Color.BlueGray4
51+
},
52+
{
53+
name: PrimaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo1000,
54+
minValue: 1000,
55+
maxValue: undefined,
56+
fillColor: Color.BlueGray4,
57+
strokeColor: Color.BlueGray4,
58+
focusColor: Color.BlueGray4
59+
}
60+
];
61+
62+
export const defaultSecondaryEdgeMetricCategories: Omit<TopologyMetricCategoryData, 'getCategoryClassName'>[] = [
63+
{
64+
name: SecondaryEdgeMetricCategoryValueType.LessThan5,
65+
minValue: 0,
66+
maxValue: 5,
67+
fillColor: Color.Gray2,
68+
strokeColor: Color.Gray2,
69+
focusColor: Color.Gray2
70+
},
71+
{
72+
name: SecondaryEdgeMetricCategoryValueType.GreaterThanOrEqualTo5,
73+
minValue: 5,
74+
maxValue: undefined,
75+
fillColor: Color.Red5,
76+
strokeColor: Color.Red5,
77+
focusColor: Color.Red5,
78+
highestPrecedence: true
79+
}
80+
];

0 commit comments

Comments
 (0)