Skip to content

Commit f573e93

Browse files
siddweikerlunny6543techknowlogick
authored
Fix heatmap activity (#15252)
* Group heatmap actions by 15 minute intervals Signed-off-by: Sidd Weiker <[email protected]> * Add multi-contribution test for user heatmap Signed-off-by: Sidd Weiker <[email protected]> * Add timezone aware summation for activity heatmap Signed-off-by: Sidd Weiker <[email protected]> * Fix api user heatmap test Signed-off-by: Sidd Weiker <[email protected]> * Update variable declaration style Co-authored-by: Lunny Xiao <[email protected]> Co-authored-by: 6543 <[email protected]> Co-authored-by: techknowlogick <[email protected]>
1 parent 3ef23d5 commit f573e93

File tree

5 files changed

+58
-18
lines changed

5 files changed

+58
-18
lines changed

integrations/api_user_heatmap_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func TestUserHeatmap(t *testing.T) {
2626
var heatmap []*models.UserHeatmapData
2727
DecodeJSON(t, resp, &heatmap)
2828
var dummyheatmap []*models.UserHeatmapData
29-
dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1603152000, Contributions: 1})
29+
dummyheatmap = append(dummyheatmap, &models.UserHeatmapData{Timestamp: 1603227600, Contributions: 1})
3030

3131
assert.Equal(t, dummyheatmap, heatmap)
3232
}

models/fixtures/action.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,27 @@
3232
repo_id: 22
3333
is_private: true
3434
created_unix: 1603267920
35+
36+
- id: 5
37+
user_id: 10
38+
op_type: 1 # create repo
39+
act_user_id: 10
40+
repo_id: 6
41+
is_private: true
42+
created_unix: 1603010100
43+
44+
- id: 6
45+
user_id: 10
46+
op_type: 1 # create repo
47+
act_user_id: 10
48+
repo_id: 7
49+
is_private: true
50+
created_unix: 1603011300
51+
52+
- id: 7
53+
user_id: 10
54+
op_type: 1 # create repo
55+
act_user_id: 10
56+
repo_id: 8
57+
is_private: false
58+
created_unix: 1603011540 # grouped with id:7

models/user_heatmap.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,14 @@ func getUserHeatmapData(user *User, team *Team, doer *User) ([]*UserHeatmapData,
3232
return hdata, nil
3333
}
3434

35-
var groupBy string
35+
// Group by 15 minute intervals which will allow the client to accurately shift the timestamp to their timezone.
36+
// The interval is based on the fact that there are timezones such as UTC +5:30 and UTC +12:45.
37+
groupBy := "created_unix / 900 * 900"
3638
groupByName := "timestamp" // We need this extra case because mssql doesn't allow grouping by alias
3739
switch {
38-
case setting.Database.UseSQLite3:
39-
groupBy = "strftime('%s', strftime('%Y-%m-%d', created_unix, 'unixepoch'))"
4040
case setting.Database.UseMySQL:
41-
groupBy = "UNIX_TIMESTAMP(DATE(FROM_UNIXTIME(created_unix)))"
42-
case setting.Database.UsePostgreSQL:
43-
groupBy = "extract(epoch from date_trunc('day', to_timestamp(created_unix)))"
41+
groupBy = "created_unix DIV 900 * 900"
4442
case setting.Database.UseMSSQL:
45-
groupBy = "datediff(SECOND, '19700101', dateadd(DAY, 0, datediff(day, 0, dateadd(s, created_unix, '19700101'))))"
4643
groupByName = groupBy
4744
}
4845

models/user_heatmap_test.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,20 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
1919
CountResult int
2020
JSONResult string
2121
}{
22-
{2, 2, 1, `[{"timestamp":1603152000,"contributions":1}]`}, // self looks at action in private repo
23-
{2, 1, 1, `[{"timestamp":1603152000,"contributions":1}]`}, // admin looks at action in private repo
24-
{2, 3, 0, `[]`}, // other user looks at action in private repo
25-
{2, 0, 0, `[]`}, // nobody looks at action in private repo
26-
{16, 15, 1, `[{"timestamp":1603238400,"contributions":1}]`}, // collaborator looks at action in private repo
27-
{3, 3, 0, `[]`}, // no action action not performed by target user
22+
// self looks at action in private repo
23+
{2, 2, 1, `[{"timestamp":1603227600,"contributions":1}]`},
24+
// admin looks at action in private repo
25+
{2, 1, 1, `[{"timestamp":1603227600,"contributions":1}]`},
26+
// other user looks at action in private repo
27+
{2, 3, 0, `[]`},
28+
// nobody looks at action in private repo
29+
{2, 0, 0, `[]`},
30+
// collaborator looks at action in private repo
31+
{16, 15, 1, `[{"timestamp":1603267200,"contributions":1}]`},
32+
// no action action not performed by target user
33+
{3, 3, 0, `[]`},
34+
// multiple actions performed with two grouped together
35+
{10, 10, 3, `[{"timestamp":1603009800,"contributions":1},{"timestamp":1603010700,"contributions":2}]`},
2836
}
2937
// Prepare
3038
assert.NoError(t, PrepareTestDatabase())
@@ -51,9 +59,13 @@ func TestGetUserHeatmapDataByUser(t *testing.T) {
5159

5260
// Get the heatmap and compare
5361
heatmap, err := GetUserHeatmapDataByUser(user, doer)
62+
var contributions int
63+
for _, hm := range heatmap {
64+
contributions += int(hm.Contributions)
65+
}
5466
assert.NoError(t, err)
55-
assert.Len(t, heatmap, len(actions), "invalid action count: did the test data became too old?")
56-
assert.Len(t, heatmap, tc.CountResult, fmt.Sprintf("testcase %d", i))
67+
assert.Len(t, actions, contributions, "invalid action count: did the test data became too old?")
68+
assert.Equal(t, tc.CountResult, contributions, fmt.Sprintf("testcase %d", i))
5769

5870
// Test JSON rendering
5971
json := jsoniter.ConfigCompatibleWithStandardLibrary

web_src/js/features/heatmap.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ export default async function initHeatmap() {
77
if (!el) return;
88

99
try {
10-
const values = JSON.parse(el.dataset.heatmapData).map(({contributions, timestamp}) => {
11-
return {date: new Date(timestamp * 1000), count: contributions};
10+
const heatmap = {};
11+
JSON.parse(el.dataset.heatmapData).forEach(({contributions, timestamp}) => {
12+
// Convert to user timezone and sum contributions by date
13+
const dateStr = new Date(timestamp * 1000).toDateString();
14+
heatmap[dateStr] = (heatmap[dateStr] || 0) + contributions;
15+
});
16+
17+
const values = Object.keys(heatmap).map((v) => {
18+
return {date: new Date(v), count: heatmap[v]};
1219
});
1320

1421
const View = Vue.extend({

0 commit comments

Comments
 (0)