Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import _ from 'lodash';
2
3import { type Dependencies } from '../../iocContainer/index.js';
4import { inject } from '../../iocContainer/utils.js';
5import {
6 isFullSubmission,
7 type ActionExecutionData,
8} from '../../rule_engine/ActionPublisher.js';
9import {
10 fromCorrelationId,
11 getSourceType,
12 type CorrelationId,
13} from '../../utils/correlationIds.js';
14import { safePick } from '../../utils/misc.js';
15import { getUtcDateOnlyString } from '../../utils/time.js';
16import {
17 type ActionExecutionMatchingRule,
18 type ActionExecutionPolicy,
19} from '../../storage/dataWarehouse/warehouseSchema.js';
20
21export type ActionExecutionSourceType =
22 | 'post-content'
23 | 'manual-action-run'
24 | 'user-rule-run'
25 | 'retroaction'
26 | 'post-items'
27 | 'mrt-decision'
28 | 'submit-report'
29 | 'submit-appeal'
30 | 'user-strike-action-execution'
31 | 'post-actions';
32
33export type ActionExecutionCorrelationId = {
34 [K in ActionExecutionSourceType]: CorrelationId<K>;
35}[ActionExecutionSourceType];
36
37export type Policy = Required<ActionExecutionPolicy>;
38export type MatchingRule = Omit<
39 ActionExecutionMatchingRule,
40 'tags' | 'policies'
41> & {
42 tags: string[];
43 policies: Policy[];
44};
45
46class ActionExecutionLogger {
47 constructor(
48 private readonly analytics: Dependencies['DataWarehouseAnalytics'],
49 ) {}
50 async logActionExecutions<T extends ActionExecutionCorrelationId>(opts: {
51 executions: ActionExecutionData<T>[];
52 failed: boolean;
53 sync?: boolean;
54 }) {
55 const { executions, failed, sync } = opts;
56 const now = new Date();
57 await this.analytics.bulkWrite(
58 'ACTION_EXECUTIONS',
59 executions.map((data) => {
60 // Remove excess properties from the matching rules and policies. We
61 // need to do this, or all kinds of junk (including json null
62 // values that cause perf problems) can end up in our warehouse table.
63 const matchingRules = data.matchingRules?.map((rule) => ({
64 ...safePick(rule, ['id', 'name', 'version', 'tags']),
65 policies: rule.policies.map((it) =>
66 safePick(it, ['id', 'name', 'userStrikeCount']),
67 ),
68 }));
69
70 return {
71 ds: getUtcDateOnlyString(now),
72 ts: now.valueOf(),
73 org_id: data.orgId,
74 action_id: data.action.id,
75 action_name: data.action.name,
76 action_source: getSourceType(data.correlationId),
77 correlation_id: fromCorrelationId(data.correlationId),
78 item_id: data.targetItem.itemId,
79 item_type_id: data.targetItem.itemType.id,
80 item_type_kind: data.targetItem.itemType.kind,
81 ...(isFullSubmission(data.targetItem)
82 ? {
83 item_submission_id: data.targetItem.submissionId,
84 item_creator_id: data.targetItem.creator?.id,
85 item_creator_type_id: data.targetItem.creator?.typeId,
86 }
87 : {}),
88 ...(matchingRules
89 ? {
90 rule_environment: data.ruleEnvironment,
91 rules: matchingRules,
92 rule_tags: _.uniq(matchingRules.flatMap((it) => it.tags)),
93 }
94 : {}),
95 policies: data.policies,
96 actor_id: data.actorId,
97 job_id: data.jobId,
98 failed,
99 };
100 }),
101 { batchTimeout: sync ? 0 : undefined },
102 );
103 }
104}
105
106export default inject(['DataWarehouseAnalytics'], ActionExecutionLogger);
107export { type ActionExecutionLogger };