Skip to content

Commit 0b9eb35

Browse files
committed
Cases user activity analytics index.
1 parent 698a3fe commit 0b9eb35

File tree

6 files changed

+291
-0
lines changed

6 files changed

+291
-0
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
9+
10+
import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-server';
11+
12+
export const CAI_ACTIVITY_INDEX_NAME = '.internal.cases-activity';
13+
14+
export const CAI_ACTIVITY_INDEX_VERSION = 2;
15+
16+
export const CAI_ACTIVITY_SOURCE_QUERY: QueryDslQueryContainer = {
17+
term: {
18+
type: 'cases-user-actions',
19+
},
20+
};
21+
22+
export const CAI_ACTIVITY_SOURCE_INDEX = ALERTING_CASES_SAVED_OBJECT_INDEX;
23+
24+
export const CAI_ACTIVITY_BACKFILL_TASK_ID = 'cai_activity_backfill_task';
25+
26+
export const CAI_ACTIVITY_SYNCHRONIZATION_TASK_ID = 'cai_cases_activity_synchronization_task';
27+
28+
export const getActivitySynchronizationSourceQuery = (
29+
lastSyncAt: Date
30+
): QueryDslQueryContainer => ({
31+
bool: {
32+
must: [
33+
{
34+
term: {
35+
type: 'cases-user-actions',
36+
},
37+
},
38+
{
39+
range: {
40+
'cases-user-actions.created_at': {
41+
gte: lastSyncAt.toISOString(),
42+
},
43+
},
44+
},
45+
],
46+
},
47+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
9+
import type { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
10+
import { AnalyticsIndex } from '../analytics_index';
11+
import {
12+
CAI_ACTIVITY_INDEX_NAME,
13+
CAI_ACTIVITY_INDEX_VERSION,
14+
CAI_ACTIVITY_SOURCE_INDEX,
15+
CAI_ACTIVITY_SOURCE_QUERY,
16+
CAI_ACTIVITY_BACKFILL_TASK_ID,
17+
CAI_ACTIVITY_SYNCHRONIZATION_TASK_ID,
18+
} from './constants';
19+
import { CAI_ACTIVITY_INDEX_MAPPINGS } from './mappings';
20+
import { CAI_ACTIVITY_INDEX_SCRIPT, CAI_ACTIVITY_INDEX_SCRIPT_ID } from './painless_scripts';
21+
import { scheduleCAISynchronizationTask } from '../tasks/synchronization_task';
22+
23+
export const createActivityAnalyticsIndex = ({
24+
esClient,
25+
logger,
26+
isServerless,
27+
taskManager,
28+
}: {
29+
esClient: ElasticsearchClient;
30+
logger: Logger;
31+
isServerless: boolean;
32+
taskManager: TaskManagerStartContract;
33+
}): AnalyticsIndex =>
34+
new AnalyticsIndex({
35+
logger,
36+
esClient,
37+
isServerless,
38+
taskManager,
39+
indexName: CAI_ACTIVITY_INDEX_NAME,
40+
indexVersion: CAI_ACTIVITY_INDEX_VERSION,
41+
mappings: CAI_ACTIVITY_INDEX_MAPPINGS,
42+
painlessScriptId: CAI_ACTIVITY_INDEX_SCRIPT_ID,
43+
painlessScript: CAI_ACTIVITY_INDEX_SCRIPT,
44+
taskId: CAI_ACTIVITY_BACKFILL_TASK_ID,
45+
sourceIndex: CAI_ACTIVITY_SOURCE_INDEX,
46+
sourceQuery: CAI_ACTIVITY_SOURCE_QUERY,
47+
});
48+
49+
export const scheduleActivityAnalyticsSyncTask = ({
50+
taskManager,
51+
logger,
52+
}: {
53+
taskManager: TaskManagerStartContract;
54+
logger: Logger;
55+
}) => {
56+
scheduleCAISynchronizationTask({
57+
taskId: CAI_ACTIVITY_SYNCHRONIZATION_TASK_ID,
58+
sourceIndex: CAI_ACTIVITY_SOURCE_INDEX,
59+
destIndex: CAI_ACTIVITY_INDEX_NAME,
60+
taskManager,
61+
logger,
62+
}).catch((e) => {
63+
logger.error(
64+
`Error scheduling ${CAI_ACTIVITY_SYNCHRONIZATION_TASK_ID} task, received ${e.message}`
65+
);
66+
});
67+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types';
9+
10+
export const CAI_ACTIVITY_INDEX_MAPPINGS: MappingTypeMapping = {
11+
dynamic: false,
12+
properties: {
13+
'@timestamp': {
14+
type: 'date',
15+
},
16+
case_id: {
17+
type: 'keyword',
18+
},
19+
action: {
20+
type: 'text',
21+
},
22+
type: {
23+
type: 'keyword',
24+
},
25+
payload: {
26+
properties: {
27+
status: {
28+
type: 'keyword',
29+
},
30+
tags: {
31+
type: 'keyword',
32+
},
33+
category: {
34+
type: 'keyword',
35+
},
36+
severity: {
37+
type: 'keyword',
38+
},
39+
},
40+
},
41+
created_at: {
42+
type: 'date',
43+
},
44+
created_at_ms: {
45+
type: 'long',
46+
},
47+
created_by: {
48+
properties: {
49+
username: {
50+
type: 'keyword',
51+
},
52+
profile_uid: {
53+
type: 'keyword',
54+
},
55+
full_name: {
56+
type: 'keyword',
57+
},
58+
email: {
59+
type: 'keyword',
60+
},
61+
},
62+
},
63+
owner: {
64+
type: 'keyword',
65+
},
66+
space_ids: {
67+
type: 'keyword',
68+
},
69+
},
70+
};
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import type { StoredScript } from '@elastic/elasticsearch/lib/api/types';
9+
import { CAI_ACTIVITY_INDEX_VERSION } from './constants';
10+
11+
export const CAI_ACTIVITY_INDEX_SCRIPT_ID = `cai_activity_script_${CAI_ACTIVITY_INDEX_VERSION}`;
12+
export const CAI_ACTIVITY_INDEX_SCRIPT: StoredScript = {
13+
lang: 'painless',
14+
source: `
15+
String statusDecoder(def x) {
16+
if (x == 0) {
17+
return "open";
18+
}
19+
if (x == 10) {
20+
return "in-progress";
21+
}
22+
if (x == 20) {
23+
return "closed";
24+
}
25+
return "";
26+
}
27+
28+
String severityDecoder(def x) {
29+
if (x == 0) {
30+
return "low"
31+
}
32+
if (x == 10) {
33+
return "medium"
34+
}
35+
if (x == 20) {
36+
return "high"
37+
}
38+
if (x == 30) {
39+
return "critical"
40+
}
41+
return ""
42+
}
43+
44+
def source = [:];
45+
source.putAll(ctx._source);
46+
ctx._source.clear();
47+
48+
ctx._source.action = source["cases-user-actions"].action;
49+
ctx._source.type = source["cases-user-actions"].type;
50+
51+
ZonedDateTime zdt_created =
52+
ZonedDateTime.parse(source["cases-user-actions"].created_at);
53+
ctx._source.created_at_ms = zdt_created.toInstant().toEpochMilli();
54+
ctx._source.created_at = source["cases-user-actions"].created_at;
55+
56+
if (source["cases-user-actions"].created_by != null) {
57+
ctx._source.created_by = new HashMap();
58+
ctx._source.created_by.full_name = source["cases-user-actions"].created_by.full_name;
59+
ctx._source.created_by.username = source["cases-user-actions"].created_by.username;
60+
ctx._source.created_by.profile_uid = source["cases-user-actions"].created_by.profile_uid;
61+
ctx._source.created_by.email = source["cases-user-actions"].created_by.email;
62+
}
63+
64+
if (source["cases-user-actions"].type != "create_case" && source["cases-user-actions"].payload != null) {
65+
ctx._source.payload = new HashMap();
66+
67+
if (source["cases-user-actions"].payload.severity != null) {
68+
ctx._source.payload.severity = source["cases-user-actions"].payload.severity;
69+
}
70+
71+
if (source["cases-user-actions"].payload.category != null) {
72+
ctx._source.payload.category = source["cases-user-actions"].payload.category;
73+
}
74+
75+
if (source["cases-user-actions"].payload.status != null) {
76+
ctx._source.payload.status = source["cases-user-actions"].payload.status;
77+
}
78+
79+
if (source["cases-user-actions"].payload.tags != null) {
80+
ctx._source.payload.tags = source["cases-user-actions"].payload.tags;
81+
}
82+
}
83+
84+
for (item in source.references) {
85+
if (item.type == "cases") {
86+
ctx._source.case_id = item.id;
87+
}
88+
}
89+
90+
ctx._source.owner = source["cases-user-actions"].owner;
91+
ctx._source.space_ids = source.namespaces;
92+
`,
93+
};

x-pack/platform/plugins/shared/cases/server/cases_analytics/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import {
1616
CAI_COMMENTS_INDEX_NAME,
1717
getCommentsSynchronizationSourceQuery,
1818
} from './comments_index/constants';
19+
import {
20+
CAI_ACTIVITY_INDEX_NAME,
21+
getActivitySynchronizationSourceQuery,
22+
} from './activity_index/constants';
1923

2024
export const CAI_NUMBER_OF_SHARDS = 1;
2125
/** Allocate 1 replica if there are enough data nodes, otherwise continue with 0 */
@@ -41,4 +45,5 @@ export const SYNCHRONIZATION_QUERIES_DICTIONARY: Record<
4145
[CAI_CASES_INDEX_NAME]: getCasesSynchronizationSourceQuery,
4246
[CAI_COMMENTS_INDEX_NAME]: getCommentsSynchronizationSourceQuery,
4347
[CAI_ATTACHMENTS_INDEX_NAME]: getAttachmentsSynchronizationSourceQuery,
48+
[CAI_ACTIVITY_INDEX_NAME]: getActivitySynchronizationSourceQuery,
4449
};

x-pack/platform/plugins/shared/cases/server/cases_analytics/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from './attachments_index';
2020
import { createCasesAnalyticsIndex, scheduleCasesAnalyticsSyncTask } from './cases_index';
2121
import { createCommentsAnalyticsIndex, scheduleCommentsAnalyticsSyncTask } from './comments_index';
22+
import { createActivityAnalyticsIndex, scheduleActivityAnalyticsSyncTask } from './activity_index';
2223

2324
export const createCasesAnalyticsIndexes = ({
2425
esClient,
@@ -49,11 +50,18 @@ export const createCasesAnalyticsIndexes = ({
4950
isServerless,
5051
taskManager,
5152
});
53+
const casesActivityIndex = createActivityAnalyticsIndex({
54+
logger,
55+
esClient,
56+
isServerless,
57+
taskManager,
58+
});
5259

5360
return Promise.all([
5461
casesIndex.upsertIndex(),
5562
casesAttachmentsIndex.upsertIndex(),
5663
casesCommentsIndex.upsertIndex(),
64+
casesActivityIndex.upsertIndex(),
5765
]);
5866
};
5967

@@ -77,6 +85,7 @@ export const scheduleCasesAnalyticsSyncTasks = ({
7785
taskManager: TaskManagerStartContract;
7886
logger: Logger;
7987
}) => {
88+
scheduleActivityAnalyticsSyncTask({ taskManager, logger });
8089
scheduleCasesAnalyticsSyncTask({ taskManager, logger });
8190
scheduleCommentsAnalyticsSyncTask({ taskManager, logger });
8291
scheduleAttachmentsAnalyticsSyncTask({ taskManager, logger });

0 commit comments

Comments
 (0)