[ResponseOps][Cases] Adding assignees telemetry (#141168)

* Adding assignees telemetry

* Addressing feedback and errors

* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Jonathan Buttner 2022-09-21 12:12:46 -04:00 committed by GitHub
parent 5fe44708a6
commit ec91f5e184
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 647 additions and 175 deletions

View file

@ -65,9 +65,14 @@ const existsSchema = s.object({
// For more details see how the types are defined in the elasticsearch javascript client:
// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295
const boolSchema = s.object({
bool: s.object({
must_not: s.oneOf([termSchema]),
}),
bool: s.oneOf([
s.object({
must_not: s.oneOf([termSchema, existsSchema]),
}),
s.object({
filter: s.oneOf([termSchema, existsSchema]),
}),
]),
});
const orderSchema = s.oneOf([

View file

@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { GENERAL_CASES_OWNER, OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER } from '../../common';
/**
* This should only be used within telemetry
*/
export const OWNERS = [OBSERVABILITY_OWNER, SECURITY_SOLUTION_OWNER, GENERAL_CASES_OWNER] as const;

View file

@ -7,6 +7,7 @@
import { SavedObjectsFindResponse } from '@kbn/core/server';
import { savedObjectsRepositoryMock, loggingSystemMock } from '@kbn/core/server/mocks';
import { CaseAggregationResult } from '../types';
import { getCasesTelemetryData } from './cases';
describe('getCasesTelemetryData', () => {
@ -50,13 +51,33 @@ describe('getCasesTelemetryData', () => {
],
};
mockFind({
const assignees = {
assigneeFilters: {
buckets: {
atLeastOne: {
doc_count: 0,
},
zero: {
doc_count: 100,
},
},
},
totalAssignees: { value: 5 },
};
const solutionValues = {
counts,
...assignees,
};
const caseAggsResult: CaseAggregationResult = {
users: { value: 1 },
tags: { value: 2 },
...assignees,
counts,
securitySolution: { counts },
observability: { counts },
cases: { counts },
securitySolution: { ...solutionValues },
observability: { ...solutionValues },
cases: { ...solutionValues },
syncAlerts: {
buckets: [
{
@ -93,7 +114,9 @@ describe('getCasesTelemetryData', () => {
},
],
},
});
};
mockFind(caseAggsResult);
mockFind({ participants: { value: 2 } });
mockFind({ references: { referenceType: { referenceAgg: { value: 3 } } } });
mockFind({ references: { referenceType: { referenceAgg: { value: 4 } } } });
@ -139,20 +162,40 @@ describe('getCasesTelemetryData', () => {
totalUsers: 1,
totalWithAlerts: 3,
totalWithConnectors: 4,
assignees: {
total: 5,
totalWithZero: 100,
totalWithAtLeastOne: 0,
},
},
main: {
assignees: {
total: 5,
totalWithZero: 100,
totalWithAtLeastOne: 0,
},
total: 1,
daily: 3,
weekly: 2,
monthly: 1,
},
obs: {
assignees: {
total: 5,
totalWithZero: 100,
totalWithAtLeastOne: 0,
},
total: 1,
daily: 3,
weekly: 2,
monthly: 1,
},
sec: {
assignees: {
total: 5,
totalWithZero: 100,
totalWithAtLeastOne: 0,
},
total: 1,
daily: 3,
weekly: 2,
@ -166,145 +209,263 @@ describe('getCasesTelemetryData', () => {
await getCasesTelemetryData({ savedObjectsClient, logger });
expect(savedObjectsClient.find.mock.calls[0][0]).toEqual({
aggs: {
cases: {
aggs: {
counts: {
date_range: {
field: 'cases.attributes.created_at',
format: 'dd/MM/YYYY',
ranges: [
{
from: 'now-1d',
to: 'now',
expect(savedObjectsClient.find.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"aggs": Object {
"assigneeFilters": Object {
"filters": Object {
"filters": Object {
"atLeastOne": Object {
"bool": Object {
"filter": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
{
from: 'now-1w',
to: 'now',
},
"zero": Object {
"bool": Object {
"must_not": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
{
from: 'now-1M',
to: 'now',
},
],
},
},
},
},
filter: {
term: {
'cases.attributes.owner': 'cases',
"cases": Object {
"aggs": Object {
"assigneeFilters": Object {
"filters": Object {
"filters": Object {
"atLeastOne": Object {
"bool": Object {
"filter": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
"zero": Object {
"bool": Object {
"must_not": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
},
},
},
"counts": Object {
"date_range": Object {
"field": "cases.attributes.created_at",
"format": "dd/MM/YYYY",
"ranges": Array [
Object {
"from": "now-1d",
"to": "now",
},
Object {
"from": "now-1w",
"to": "now",
},
Object {
"from": "now-1M",
"to": "now",
},
],
},
},
"totalAssignees": Object {
"value_count": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
},
counts: {
date_range: {
field: 'cases.attributes.created_at',
format: 'dd/MM/YYYY',
ranges: [
{
from: 'now-1d',
to: 'now',
},
{
from: 'now-1w',
to: 'now',
},
{
from: 'now-1M',
to: 'now',
},
],
},
},
observability: {
aggs: {
counts: {
date_range: {
field: 'cases.attributes.created_at',
format: 'dd/MM/YYYY',
ranges: [
{
from: 'now-1d',
to: 'now',
},
{
from: 'now-1w',
to: 'now',
},
{
from: 'now-1M',
to: 'now',
},
],
"filter": Object {
"term": Object {
"cases.attributes.owner": "cases",
},
},
},
filter: {
term: {
'cases.attributes.owner': 'observability',
"counts": Object {
"date_range": Object {
"field": "cases.attributes.created_at",
"format": "dd/MM/YYYY",
"ranges": Array [
Object {
"from": "now-1d",
"to": "now",
},
Object {
"from": "now-1w",
"to": "now",
},
Object {
"from": "now-1M",
"to": "now",
},
],
},
},
},
securitySolution: {
aggs: {
counts: {
date_range: {
field: 'cases.attributes.created_at',
format: 'dd/MM/YYYY',
ranges: [
{
from: 'now-1d',
to: 'now',
"observability": Object {
"aggs": Object {
"assigneeFilters": Object {
"filters": Object {
"filters": Object {
"atLeastOne": Object {
"bool": Object {
"filter": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
"zero": Object {
"bool": Object {
"must_not": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
},
{
from: 'now-1w',
to: 'now',
},
{
from: 'now-1M',
to: 'now',
},
],
},
},
"counts": Object {
"date_range": Object {
"field": "cases.attributes.created_at",
"format": "dd/MM/YYYY",
"ranges": Array [
Object {
"from": "now-1d",
"to": "now",
},
Object {
"from": "now-1w",
"to": "now",
},
Object {
"from": "now-1M",
"to": "now",
},
],
},
},
"totalAssignees": Object {
"value_count": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
"filter": Object {
"term": Object {
"cases.attributes.owner": "observability",
},
},
},
filter: {
term: {
'cases.attributes.owner': 'securitySolution',
"securitySolution": Object {
"aggs": Object {
"assigneeFilters": Object {
"filters": Object {
"filters": Object {
"atLeastOne": Object {
"bool": Object {
"filter": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
"zero": Object {
"bool": Object {
"must_not": Object {
"exists": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
},
},
},
},
"counts": Object {
"date_range": Object {
"field": "cases.attributes.created_at",
"format": "dd/MM/YYYY",
"ranges": Array [
Object {
"from": "now-1d",
"to": "now",
},
Object {
"from": "now-1w",
"to": "now",
},
Object {
"from": "now-1M",
"to": "now",
},
],
},
},
"totalAssignees": Object {
"value_count": Object {
"field": "cases.attributes.assignees.uid",
},
},
},
"filter": Object {
"term": Object {
"cases.attributes.owner": "securitySolution",
},
},
},
"status": Object {
"terms": Object {
"field": "cases.attributes.status",
},
},
"syncAlerts": Object {
"terms": Object {
"field": "cases.attributes.settings.syncAlerts",
},
},
"tags": Object {
"cardinality": Object {
"field": "cases.attributes.tags",
},
},
"totalAssignees": Object {
"value_count": Object {
"field": "cases.attributes.assignees.uid",
},
},
"totalsByOwner": Object {
"terms": Object {
"field": "cases.attributes.owner",
},
},
"users": Object {
"cardinality": Object {
"field": "cases.attributes.created_by.username",
},
},
},
status: {
terms: {
field: 'cases.attributes.status',
},
},
syncAlerts: {
terms: {
field: 'cases.attributes.settings.syncAlerts',
},
},
tags: {
cardinality: {
field: 'cases.attributes.tags',
},
},
totalsByOwner: {
terms: {
field: 'cases.attributes.owner',
},
},
users: {
cardinality: {
field: 'cases.attributes.created_by.username',
},
},
},
page: 0,
perPage: 0,
type: 'cases',
});
"page": 0,
"perPage": 0,
"type": "cases",
}
`);
expect(savedObjectsClient.find.mock.calls[1][0]).toEqual({
aggs: {

View file

@ -11,6 +11,7 @@ import {
CASE_USER_ACTION_SAVED_OBJECT,
} from '../../../common/constants';
import { ESCaseAttributes } from '../../services/cases/types';
import { OWNERS } from '../constants';
import {
CollectTelemetryDataParams,
Buckets,
@ -18,6 +19,7 @@ import {
Cardinality,
ReferencesAggregation,
LatestDates,
CaseAggregationResult,
} from '../types';
import {
findValueInBuckets,
@ -27,6 +29,7 @@ import {
getOnlyAlertsCommentsFilter,
getOnlyConnectorsFilter,
getReferencesAggregationQuery,
getSolutionValues,
} from './utils';
export const getLatestCasesDates = async ({
@ -58,8 +61,7 @@ export const getCasesTelemetryData = async ({
savedObjectsClient,
logger,
}: CollectTelemetryDataParams): Promise<CasesTelemetry['cases']> => {
const owners = ['observability', 'securitySolution', 'cases'] as const;
const byOwnerAggregationQuery = owners.reduce(
const byOwnerAggregationQuery = OWNERS.reduce(
(aggQuery, owner) => ({
...aggQuery,
[owner]: {
@ -68,28 +70,23 @@ export const getCasesTelemetryData = async ({
[`${CASE_SAVED_OBJECT}.attributes.owner`]: owner,
},
},
aggs: getCountsAggregationQuery(CASE_SAVED_OBJECT),
aggs: {
...getCountsAggregationQuery(CASE_SAVED_OBJECT),
...getAssigneesAggregations(),
},
},
}),
{}
);
const casesRes = await savedObjectsClient.find<
unknown,
Record<typeof owners[number], { counts: Buckets }> & {
counts: Buckets;
syncAlerts: Buckets;
status: Buckets;
users: Cardinality;
tags: Cardinality;
}
>({
const casesRes = await savedObjectsClient.find<unknown, CaseAggregationResult>({
page: 0,
perPage: 0,
type: CASE_SAVED_OBJECT,
aggs: {
...byOwnerAggregationQuery,
...getCountsAggregationQuery(CASE_SAVED_OBJECT),
...getAssigneesAggregations(),
totalsByOwner: {
terms: { field: `${CASE_SAVED_OBJECT}.attributes.owner` },
},
@ -116,7 +113,7 @@ export const getCasesTelemetryData = async ({
const commentsRes = await savedObjectsClient.find<
unknown,
Record<typeof owners[number], { counts: Buckets }> & {
Record<typeof OWNERS[number], { counts: Buckets }> & {
participants: Cardinality;
} & ReferencesAggregation
>({
@ -164,16 +161,7 @@ export const getCasesTelemetryData = async ({
const aggregationsBuckets = getAggregationsBuckets({
aggs: casesRes.aggregations,
keys: [
'counts',
'observability.counts',
'securitySolution.counts',
'cases.counts',
'syncAlerts',
'status',
'totalsByOwner',
'users',
],
keys: ['counts', 'syncAlerts', 'status', 'users', 'totalAssignees'],
});
return {
@ -195,18 +183,47 @@ export const getCasesTelemetryData = async ({
totalWithConnectors:
totalConnectorsRes.aggregations?.references?.referenceType?.referenceAgg?.value ?? 0,
latestDates,
assignees: {
total: casesRes.aggregations?.totalAssignees.value ?? 0,
totalWithZero: casesRes.aggregations?.assigneeFilters.buckets.zero.doc_count ?? 0,
totalWithAtLeastOne:
casesRes.aggregations?.assigneeFilters.buckets.atLeastOne.doc_count ?? 0,
},
},
sec: {
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'securitySolution'),
...getCountsFromBuckets(aggregationsBuckets['securitySolution.counts']),
},
obs: {
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'observability'),
...getCountsFromBuckets(aggregationsBuckets['observability.counts']),
},
main: {
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, 'cases'),
...getCountsFromBuckets(aggregationsBuckets['cases.counts']),
},
sec: getSolutionValues(casesRes.aggregations, 'securitySolution'),
obs: getSolutionValues(casesRes.aggregations, 'observability'),
main: getSolutionValues(casesRes.aggregations, 'cases'),
};
};
const getAssigneesAggregations = () => ({
totalAssignees: {
value_count: {
field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`,
},
},
assigneeFilters: {
filters: {
filters: {
zero: {
bool: {
must_not: {
exists: {
field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`,
},
},
},
},
atLeastOne: {
bool: {
filter: {
exists: {
field: `${CASE_SAVED_OBJECT}.attributes.assignees.uid`,
},
},
},
},
},
},
},
});

View file

@ -6,6 +6,7 @@
*/
import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks';
import { CaseAggregationResult } from '../types';
import {
findValueInBuckets,
getAggregationsBuckets,
@ -18,9 +19,127 @@ import {
getOnlyAlertsCommentsFilter,
getOnlyConnectorsFilter,
getReferencesAggregationQuery,
getSolutionValues,
} from './utils';
describe('utils', () => {
describe('getSolutionValues', () => {
const counts = {
buckets: [
{ doc_count: 1, key: 1 },
{ doc_count: 2, key: 2 },
{ doc_count: 3, key: 3 },
],
};
const assignees = {
assigneeFilters: {
buckets: {
atLeastOne: {
doc_count: 0,
},
zero: {
doc_count: 100,
},
},
},
totalAssignees: { value: 5 },
};
const solutionValues = {
counts,
...assignees,
};
const aggsResult: CaseAggregationResult = {
users: { value: 1 },
tags: { value: 2 },
...assignees,
counts,
securitySolution: { ...solutionValues },
observability: { ...solutionValues },
cases: { ...solutionValues },
syncAlerts: {
buckets: [
{
key: 0,
doc_count: 1,
},
{
key: 1,
doc_count: 1,
},
],
},
status: {
buckets: [
{
key: 'open',
doc_count: 2,
},
],
},
totalsByOwner: {
buckets: [
{
key: 'observability',
doc_count: 1,
},
{
key: 'securitySolution',
doc_count: 1,
},
{
key: 'cases',
doc_count: 1,
},
],
},
};
it('constructs the solution values correctly', () => {
expect(getSolutionValues(aggsResult, 'securitySolution')).toMatchInlineSnapshot(`
Object {
"assignees": Object {
"total": 5,
"totalWithAtLeastOne": 0,
"totalWithZero": 100,
},
"daily": 3,
"monthly": 1,
"total": 1,
"weekly": 2,
}
`);
expect(getSolutionValues(aggsResult, 'cases')).toMatchInlineSnapshot(`
Object {
"assignees": Object {
"total": 5,
"totalWithAtLeastOne": 0,
"totalWithZero": 100,
},
"daily": 3,
"monthly": 1,
"total": 1,
"weekly": 2,
}
`);
expect(getSolutionValues(aggsResult, 'observability')).toMatchInlineSnapshot(`
Object {
"assignees": Object {
"total": 5,
"totalWithAtLeastOne": 0,
"totalWithZero": 100,
},
"daily": 3,
"monthly": 1,
"total": 1,
"weekly": 2,
}
`);
});
});
describe('getCountsAggregationQuery', () => {
it('returns the correct query', () => {
expect(getCountsAggregationQuery('test')).toEqual({

View file

@ -13,8 +13,15 @@ import {
CASE_SAVED_OBJECT,
CASE_USER_ACTION_SAVED_OBJECT,
} from '../../../common/constants';
import { Buckets, CasesTelemetry, MaxBucketOnCaseAggregation } from '../types';
import {
CaseAggregationResult,
Buckets,
CasesTelemetry,
MaxBucketOnCaseAggregation,
SolutionTelemetry,
} from '../types';
import { buildFilter } from '../../client/utils';
import { OWNERS } from '../constants';
export const getCountsAggregationQuery = (savedObjectType: string) => ({
counts: {
@ -147,6 +154,26 @@ export const getBucketFromAggregation = ({
aggs?: Record<string, unknown>;
}): Buckets['buckets'] => (get(aggs, `${key}.buckets`) ?? []) as Buckets['buckets'];
export const getSolutionValues = (
aggregations: CaseAggregationResult | undefined,
owner: typeof OWNERS[number]
): SolutionTelemetry => {
const aggregationsBuckets = getAggregationsBuckets({
aggs: aggregations,
keys: ['totalsByOwner', 'securitySolution.counts', 'observability.counts', 'cases.counts'],
});
return {
total: findValueInBuckets(aggregationsBuckets.totalsByOwner, owner),
...getCountsFromBuckets(aggregationsBuckets[`${owner}.counts`]),
assignees: {
total: aggregations?.[owner].totalAssignees.value ?? 0,
totalWithZero: aggregations?.[owner].assigneeFilters.buckets.zero.doc_count ?? 0,
totalWithAtLeastOne: aggregations?.[owner].assigneeFilters.buckets.atLeastOne.doc_count ?? 0,
},
};
};
export const findValueInBuckets = (buckets: Buckets['buckets'], value: string | number): number =>
buckets.find(({ key }) => key === value)?.doc_count ?? 0;
@ -184,6 +211,11 @@ export const getOnlyConnectorsFilter = () =>
export const getTelemetryDataEmptyState = (): CasesTelemetry => ({
cases: {
all: {
assignees: {
total: 0,
totalWithZero: 0,
totalWithAtLeastOne: 0,
},
total: 0,
monthly: 0,
weekly: 0,
@ -206,9 +238,27 @@ export const getTelemetryDataEmptyState = (): CasesTelemetry => ({
closedAt: null,
},
},
sec: { total: 0, monthly: 0, weekly: 0, daily: 0 },
obs: { total: 0, monthly: 0, weekly: 0, daily: 0 },
main: { total: 0, monthly: 0, weekly: 0, daily: 0 },
sec: {
total: 0,
monthly: 0,
weekly: 0,
daily: 0,
assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 },
},
obs: {
total: 0,
monthly: 0,
weekly: 0,
daily: 0,
assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 },
},
main: {
total: 0,
monthly: 0,
weekly: 0,
daily: 0,
assignees: { total: 0, totalWithAtLeastOne: 0, totalWithZero: 0 },
},
},
userActions: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } },
comments: { all: { total: 0, monthly: 0, weekly: 0, daily: 0, maxOnACase: 0 } },

View file

@ -12,6 +12,8 @@ import {
StatusSchema,
LatestDatesSchema,
TypeString,
SolutionTelemetrySchema,
AssigneesSchema,
} from './types';
const long: TypeLong = { type: 'long' };
@ -24,6 +26,17 @@ const countSchema: CountSchema = {
daily: long,
};
const assigneesSchema: AssigneesSchema = {
total: long,
totalWithZero: long,
totalWithAtLeastOne: long,
};
const solutionTelemetry: SolutionTelemetrySchema = {
...countSchema,
assignees: assigneesSchema,
};
const statusSchema: StatusSchema = {
open: long,
inProgress: long,
@ -40,6 +53,7 @@ export const casesSchema: CasesTelemetrySchema = {
cases: {
all: {
...countSchema,
assignees: assigneesSchema,
status: statusSchema,
syncAlertsOn: long,
syncAlertsOff: long,
@ -50,9 +64,9 @@ export const casesSchema: CasesTelemetrySchema = {
totalWithConnectors: long,
latestDates: latestDatesSchema,
},
sec: countSchema,
obs: countSchema,
main: countSchema,
sec: solutionTelemetry,
obs: solutionTelemetry,
main: solutionTelemetry,
},
userActions: { all: { ...countSchema, maxOnACase: long } },
comments: { all: { ...countSchema, maxOnACase: long } },

View file

@ -7,6 +7,7 @@
import { ISavedObjectsRepository, Logger } from '@kbn/core/server';
import { MakeSchemaFrom } from '@kbn/usage-collection-plugin/server';
import { OWNERS } from './constants';
export interface Buckets {
buckets: Array<{
@ -19,6 +20,8 @@ export interface Cardinality {
value: number;
}
export type ValueCount = Cardinality;
export interface MaxBucketOnCaseAggregation {
references: { cases: { max: { value: number } } };
}
@ -47,6 +50,41 @@ export interface Count {
daily: number;
}
export interface AssigneesFilters {
buckets: {
zero: { doc_count: number };
atLeastOne: { doc_count: number };
};
}
export type CaseAggregationResult = Record<
typeof OWNERS[number],
{
counts: Buckets;
totalAssignees: ValueCount;
assigneeFilters: AssigneesFilters;
}
> & {
assigneeFilters: AssigneesFilters;
counts: Buckets;
syncAlerts: Buckets;
status: Buckets;
users: Cardinality;
tags: Cardinality;
totalAssignees: ValueCount;
totalsByOwner: Buckets;
};
export interface Assignees {
total: number;
totalWithZero: number;
totalWithAtLeastOne: number;
}
export interface SolutionTelemetry extends Count {
assignees: Assignees;
}
export interface Status {
open: number;
inProgress: number;
@ -62,6 +100,7 @@ export interface LatestDates {
export interface CasesTelemetry {
cases: {
all: Count & {
assignees: Assignees;
status: Status;
syncAlertsOn: number;
syncAlertsOff: number;
@ -72,9 +111,9 @@ export interface CasesTelemetry {
totalWithConnectors: number;
latestDates: LatestDates;
};
sec: Count;
obs: Count;
main: Count;
sec: SolutionTelemetry;
obs: SolutionTelemetry;
main: SolutionTelemetry;
};
userActions: { all: Count & { maxOnACase: number } };
comments: { all: Count & { maxOnACase: number } };
@ -107,3 +146,5 @@ export type CountSchema = MakeSchemaFrom<Count>;
export type StatusSchema = MakeSchemaFrom<Status>;
export type LatestDatesSchema = MakeSchemaFrom<LatestDates>;
export type CasesTelemetrySchema = MakeSchemaFrom<CasesTelemetry>;
export type AssigneesSchema = MakeSchemaFrom<Assignees>;
export type SolutionTelemetrySchema = MakeSchemaFrom<SolutionTelemetry>;

View file

@ -4644,6 +4644,19 @@
"properties": {
"all": {
"properties": {
"assignees": {
"properties": {
"total": {
"type": "long"
},
"totalWithZero": {
"type": "long"
},
"totalWithAtLeastOne": {
"type": "long"
}
}
},
"total": {
"type": "long"
},
@ -4707,6 +4720,19 @@
},
"sec": {
"properties": {
"assignees": {
"properties": {
"total": {
"type": "long"
},
"totalWithZero": {
"type": "long"
},
"totalWithAtLeastOne": {
"type": "long"
}
}
},
"total": {
"type": "long"
},
@ -4723,6 +4749,19 @@
},
"obs": {
"properties": {
"assignees": {
"properties": {
"total": {
"type": "long"
},
"totalWithZero": {
"type": "long"
},
"totalWithAtLeastOne": {
"type": "long"
}
}
},
"total": {
"type": "long"
},
@ -4739,6 +4778,19 @@
},
"main": {
"properties": {
"assignees": {
"properties": {
"total": {
"type": "long"
},
"totalWithZero": {
"type": "long"
},
"totalWithAtLeastOne": {
"type": "long"
}
}
},
"total": {
"type": "long"
},