Skip to content

Commit 3f5f408

Browse files
committed
feat: Add a work_duration_ms computed in analytics job
1 parent 2b6776e commit 3f5f408

7 files changed

Lines changed: 318 additions & 298 deletions

File tree

analytics/src/main/java/stats/UserAggregateStat.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import models.EnrichedFileSegment;
88

99
public class UserAggregateStat implements IStat {
10-
public final static String[] PRIMITIVE_COLUMNS = { "user_id", "window_start", "window_end" };
10+
public final static String[] PRIMITIVE_COLUMNS = { "user_id", "window_start", "window_end", "work_duration_ms" };
1111
public final static String[] JSONB_COLUMNS = { "lang_durations", "machine_durations", "editor_durations",
1212
"project_durations", "activity_durations" };
1313
public final static String CONFLICT_KEYS = "user_id, window_start";
@@ -77,6 +77,7 @@ public HashMap<String, Long> getActivityDurations() {
7777
public Map<String, Object> asRecord() {
7878
Map<String, Object> map = new HashMap<>();
7979
map.put("user_id", this.user_id);
80+
map.put("work_duration_ms", this.total_duration);
8081
map.put("lang_durations", this.language_durations);
8182
map.put("machine_durations", this.machine_durations);
8283
map.put("editor_durations", this.editor_durations);

analytics/src/main/java/stats/UserProjectAggregateStat.java

Lines changed: 166 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -7,168 +7,170 @@
77
import models.EnrichedFileSegment;
88

99
public class UserProjectAggregateStat implements IStat {
10-
public static final String[] PRIMITIVE_COLUMNS = { "user_id", "project_path", "window_start", "window_end", };
11-
public static final String[] JSONB_COLUMNS = { "lang_durations", "machine_durations", "editor_durations",
12-
"activity_durations", "files_durations" };
13-
public static final String[] CONFLICT_KEYS = { "user_id", "project_path", "window_start" };
14-
private final int user_id;
15-
private final String project_path;
16-
private long total_duration;
17-
private Instant window_start;
18-
private Instant window_end;
19-
20-
private final HashMap<String, Long> machine_durations = new HashMap<>();
21-
private final HashMap<String, Long> language_durations = new HashMap<>();
22-
private final HashMap<String, Long> editor_durations = new HashMap<>();
23-
private final HashMap<String, Long> project_durations = new HashMap<>();
24-
private final HashMap<String, Long> activity_durations = new HashMap<>();
25-
private final HashMap<String, Long> files_durations = new HashMap<>();
26-
27-
public UserProjectAggregateStat(int user_id, String project_path) {
28-
this.user_id = user_id;
29-
this.project_path = project_path;
30-
this.total_duration = 0;
31-
this.window_start = null;
32-
this.window_end = null;
33-
}
34-
35-
public int getUserId() {
36-
return user_id;
37-
}
38-
39-
public String getProjectPath() {
40-
return project_path;
41-
}
42-
43-
public long getTotalDuration() {
44-
return total_duration;
45-
}
46-
47-
public Instant getWindowStart() {
48-
return window_start;
49-
}
50-
51-
public Instant getWindowEnd() {
52-
return window_end;
53-
}
54-
55-
public void setWindowStart(Instant start) {
56-
this.window_start = start;
57-
}
58-
59-
public void setWindowEnd(Instant end) {
60-
this.window_end = end;
61-
}
62-
63-
public HashMap<String, Long> getMachineDurations() {
64-
return machine_durations;
65-
}
66-
67-
public HashMap<String, Long> getLangDurations() {
68-
return language_durations;
69-
}
70-
71-
public HashMap<String, Long> getEditorDurations() {
72-
return editor_durations;
73-
}
74-
75-
public HashMap<String, Long> getProjectDurations() {
76-
return project_durations;
77-
}
78-
79-
public HashMap<String, Long> getActivityDurations() {
80-
return activity_durations;
81-
}
82-
83-
public HashMap<String, Long> getFilesDurations() {
84-
return files_durations;
85-
}
86-
87-
@Override
88-
public Map<String, Object> asRecord() {
89-
Map<String, Object> map = new HashMap<>();
90-
map.put("user_id", this.user_id);
91-
map.put("project_path", this.project_path);
92-
map.put("lang_durations", this.language_durations);
93-
map.put("machine_durations", this.machine_durations);
94-
map.put("editor_durations", this.editor_durations);
95-
map.put("project_durations", this.project_durations);
96-
map.put("activity_durations", this.activity_durations);
97-
map.put("file_durations", this.files_durations);
98-
map.put("window_start", this.window_start);
99-
map.put("window_end", this.window_end);
100-
return map;
101-
}
102-
103-
public void add(EnrichedFileSegment seg) {
104-
long duration = Duration.between(Instant.parse(seg.getStart_time()), Instant.parse(seg.getEnd_time()))
105-
.toMillis();
106-
107-
this.total_duration += duration;
108-
109-
if (seg.getLang() != null)
110-
language_durations.merge(seg.getLang(), duration, Long::sum);
111-
112-
if (seg.getEditor() != null)
113-
editor_durations.merge(seg.getEditor(), duration, Long::sum);
114-
115-
if (seg.getMachine_name() != null)
116-
machine_durations.merge(seg.getMachine_name(), duration, Long::sum);
117-
118-
if (seg.getProject_name() != null)
119-
project_durations.merge(seg.getProject_name(), duration, Long::sum);
120-
121-
if (seg.getSegment_type() != null)
122-
activity_durations.merge(seg.getSegment_type(), duration, Long::sum);
123-
124-
if (seg.getFile_path() != null)
125-
files_durations.merge(seg.getFile_path(), duration, Long::sum);
126-
127-
// Expand window boundaries
128-
Instant start = Instant.parse(seg.getStart_time());
129-
Instant end = Instant.parse(seg.getEnd_time());
130-
if (window_start == null || start.isBefore(window_start))
131-
window_start = start;
132-
if (window_end == null || end.isAfter(window_end))
133-
window_end = end;
134-
}
135-
136-
public void postProcess(WindowContext ctx) {
137-
setWindowStart(ctx.getWindowStart());
138-
setWindowEnd(ctx.getWindowEnd());
139-
}
140-
141-
@Override
142-
public String toString() {
143-
StringBuilder sb = new StringBuilder();
144-
sb.append("UserProjectStat{user_id=").append(user_id).append(", project_path=").append(project_path)
145-
.append(", total_duration=").append(total_duration).append(" ms");
146-
147-
if (window_start != null && window_end != null) {
148-
sb.append(", window=[").append(window_start).append(" → ").append(window_end).append("]");
149-
}
150-
151-
appendCategory(sb, "files", files_durations);
152-
appendCategory(sb, "languages", language_durations);
153-
appendCategory(sb, "machines", machine_durations);
154-
appendCategory(sb, "projects", project_durations);
155-
appendCategory(sb, "activities", activity_durations);
156-
appendCategory(sb, "editor", editor_durations);
157-
158-
sb.append("}");
159-
return sb.toString();
160-
}
161-
162-
private void appendCategory(StringBuilder sb, String name, HashMap<String, Long> durations) {
163-
if (!durations.isEmpty()) {
164-
sb.append(", ").append(name).append("={");
165-
for (Map.Entry<String, Long> entry : durations.entrySet()) {
166-
double percent = (total_duration > 0) ? (entry.getValue() * 100.0 / total_duration) : 0.0;
167-
sb.append(entry.getKey()).append(": ").append(String.format("%.2f%%", percent)).append(" (")
168-
.append(entry.getValue()).append(" ms), ");
169-
}
170-
sb.setLength(sb.length() - 2);
171-
sb.append("}");
172-
}
173-
}
10+
public static final String[] PRIMITIVE_COLUMNS = { "user_id", "project_path", "window_start", "window_end",
11+
"work_duration_ms" };
12+
public static final String[] JSONB_COLUMNS = { "lang_durations", "machine_durations", "editor_durations",
13+
"activity_durations", "files_durations" };
14+
public static final String[] CONFLICT_KEYS = { "user_id", "project_path", "window_start" };
15+
private final int user_id;
16+
private final String project_path;
17+
private long total_duration;
18+
private Instant window_start;
19+
private Instant window_end;
20+
21+
private final HashMap<String, Long> machine_durations = new HashMap<>();
22+
private final HashMap<String, Long> language_durations = new HashMap<>();
23+
private final HashMap<String, Long> editor_durations = new HashMap<>();
24+
private final HashMap<String, Long> project_durations = new HashMap<>();
25+
private final HashMap<String, Long> activity_durations = new HashMap<>();
26+
private final HashMap<String, Long> files_durations = new HashMap<>();
27+
28+
public UserProjectAggregateStat(int user_id, String project_path) {
29+
this.user_id = user_id;
30+
this.project_path = project_path;
31+
this.total_duration = 0;
32+
this.window_start = null;
33+
this.window_end = null;
34+
}
35+
36+
public int getUserId() {
37+
return user_id;
38+
}
39+
40+
public String getProjectPath() {
41+
return project_path;
42+
}
43+
44+
public long getTotalDuration() {
45+
return total_duration;
46+
}
47+
48+
public Instant getWindowStart() {
49+
return window_start;
50+
}
51+
52+
public Instant getWindowEnd() {
53+
return window_end;
54+
}
55+
56+
public void setWindowStart(Instant start) {
57+
this.window_start = start;
58+
}
59+
60+
public void setWindowEnd(Instant end) {
61+
this.window_end = end;
62+
}
63+
64+
public HashMap<String, Long> getMachineDurations() {
65+
return machine_durations;
66+
}
67+
68+
public HashMap<String, Long> getLangDurations() {
69+
return language_durations;
70+
}
71+
72+
public HashMap<String, Long> getEditorDurations() {
73+
return editor_durations;
74+
}
75+
76+
public HashMap<String, Long> getProjectDurations() {
77+
return project_durations;
78+
}
79+
80+
public HashMap<String, Long> getActivityDurations() {
81+
return activity_durations;
82+
}
83+
84+
public HashMap<String, Long> getFilesDurations() {
85+
return files_durations;
86+
}
87+
88+
@Override
89+
public Map<String, Object> asRecord() {
90+
Map<String, Object> map = new HashMap<>();
91+
map.put("user_id", this.user_id);
92+
map.put("work_duration_ms", this.total_duration);
93+
map.put("project_path", this.project_path);
94+
map.put("lang_durations", this.language_durations);
95+
map.put("machine_durations", this.machine_durations);
96+
map.put("editor_durations", this.editor_durations);
97+
map.put("project_durations", this.project_durations);
98+
map.put("activity_durations", this.activity_durations);
99+
map.put("file_durations", this.files_durations);
100+
map.put("window_start", this.window_start);
101+
map.put("window_end", this.window_end);
102+
return map;
103+
}
104+
105+
public void add(EnrichedFileSegment seg) {
106+
long duration = Duration.between(Instant.parse(seg.getStart_time()), Instant.parse(seg.getEnd_time()))
107+
.toMillis();
108+
109+
this.total_duration += duration;
110+
111+
if (seg.getLang() != null)
112+
language_durations.merge(seg.getLang(), duration, Long::sum);
113+
114+
if (seg.getEditor() != null)
115+
editor_durations.merge(seg.getEditor(), duration, Long::sum);
116+
117+
if (seg.getMachine_name() != null)
118+
machine_durations.merge(seg.getMachine_name(), duration, Long::sum);
119+
120+
if (seg.getProject_name() != null)
121+
project_durations.merge(seg.getProject_name(), duration, Long::sum);
122+
123+
if (seg.getSegment_type() != null)
124+
activity_durations.merge(seg.getSegment_type(), duration, Long::sum);
125+
126+
if (seg.getFile_path() != null)
127+
files_durations.merge(seg.getFile_path(), duration, Long::sum);
128+
129+
// Expand window boundaries
130+
Instant start = Instant.parse(seg.getStart_time());
131+
Instant end = Instant.parse(seg.getEnd_time());
132+
if (window_start == null || start.isBefore(window_start))
133+
window_start = start;
134+
if (window_end == null || end.isAfter(window_end))
135+
window_end = end;
136+
}
137+
138+
public void postProcess(WindowContext ctx) {
139+
setWindowStart(ctx.getWindowStart());
140+
setWindowEnd(ctx.getWindowEnd());
141+
}
142+
143+
@Override
144+
public String toString() {
145+
StringBuilder sb = new StringBuilder();
146+
sb.append("UserProjectStat{user_id=").append(user_id).append(", project_path=").append(project_path)
147+
.append(", total_duration=").append(total_duration).append(" ms");
148+
149+
if (window_start != null && window_end != null) {
150+
sb.append(", window=[").append(window_start).append(" → ").append(window_end).append("]");
151+
}
152+
153+
appendCategory(sb, "files", files_durations);
154+
appendCategory(sb, "languages", language_durations);
155+
appendCategory(sb, "machines", machine_durations);
156+
appendCategory(sb, "projects", project_durations);
157+
appendCategory(sb, "activities", activity_durations);
158+
appendCategory(sb, "editor", editor_durations);
159+
160+
sb.append("}");
161+
return sb.toString();
162+
}
163+
164+
private void appendCategory(StringBuilder sb, String name, HashMap<String, Long> durations) {
165+
if (!durations.isEmpty()) {
166+
sb.append(", ").append(name).append("={");
167+
for (Map.Entry<String, Long> entry : durations.entrySet()) {
168+
double percent = (total_duration > 0) ? (entry.getValue() * 100.0 / total_duration) : 0.0;
169+
sb.append(entry.getKey()).append(": ").append(String.format("%.2f%%", percent)).append(" (")
170+
.append(entry.getValue()).append(" ms), ");
171+
}
172+
sb.setLength(sb.length() - 2);
173+
sb.append("}");
174+
}
175+
}
174176
}

0 commit comments

Comments
 (0)