Commit 9b935080 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '27616-fix-contributions-graph-utc-offset-mysql' into 'master'

Fix Timezone Inconsistencies in User Contribution Calendar

Closes #27616 and #1943

See merge request !13208
parents d1f27076 013fe764
...@@ -7,6 +7,14 @@ const LOADING_HTML = ` ...@@ -7,6 +7,14 @@ const LOADING_HTML = `
</div> </div>
`; `;
function getSystemDate(systemUtcOffsetSeconds) {
const date = new Date();
const localUtcOffsetMinutes = 0 - date.getTimezoneOffset();
const systemUtcOffsetMinutes = systemUtcOffsetSeconds / 60;
date.setMinutes((date.getMinutes() - localUtcOffsetMinutes) + systemUtcOffsetMinutes);
return date;
}
function formatTooltipText({ date, count }) { function formatTooltipText({ date, count }) {
const dateObject = new Date(date); const dateObject = new Date(date);
const dateDayName = gl.utils.getDayName(dateObject); const dateDayName = gl.utils.getDayName(dateObject);
...@@ -22,7 +30,7 @@ function formatTooltipText({ date, count }) { ...@@ -22,7 +30,7 @@ function formatTooltipText({ date, count }) {
const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]); const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
export default class ActivityCalendar { export default class ActivityCalendar {
constructor(container, timestamps, calendarActivitiesPath) { constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0) {
this.calendarActivitiesPath = calendarActivitiesPath; this.calendarActivitiesPath = calendarActivitiesPath;
this.clickDay = this.clickDay.bind(this); this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = ''; this.currentSelectedDate = '';
...@@ -37,7 +45,7 @@ export default class ActivityCalendar { ...@@ -37,7 +45,7 @@ export default class ActivityCalendar {
this.timestampsTmp = []; this.timestampsTmp = [];
let group = 0; let group = 0;
const today = new Date(); const today = getSystemDate(utcOffset);
today.setHours(0, 0, 0, 0, 0); today.setHours(0, 0, 0, 0, 0);
const oneYearAgo = new Date(today); const oneYearAgo = new Date(today);
......
...@@ -150,15 +150,21 @@ export default class UserTabs { ...@@ -150,15 +150,21 @@ export default class UserTabs {
const $calendarWrap = this.$parentEl.find('.user-calendar'); const $calendarWrap = this.$parentEl.find('.user-calendar');
const calendarPath = $calendarWrap.data('calendarPath'); const calendarPath = $calendarWrap.data('calendarPath');
const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
const utcOffset = $calendarWrap.data('utcOffset');
let utcFormatted = 'UTC';
if (utcOffset !== 0) {
utcFormatted = `UTC${utcOffset > 0 ? '+' : ''}${(utcOffset / 3600)}`;
}
$.ajax({ $.ajax({
dataType: 'json', dataType: 'json',
url: calendarPath, url: calendarPath,
success: (activityData) => { success: (activityData) => {
$calendarWrap.html(CALENDAR_TEMPLATE); $calendarWrap.html(CALENDAR_TEMPLATE);
$calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath); new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath, utcOffset);
}, },
}); });
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
margin-top: -23px; margin-top: -23px;
float: right; float: right;
font-size: 12px; font-size: 12px;
direction: ltr;
} }
.pika-single.gitlab-theme { .pika-single.gitlab-theme {
......
%h4.prepend-top-20 %h4.prepend-top-20
Contributions for Contributions for
%strong= @calendar_date.to_s(:short) %strong= @calendar_date.to_s(:medium)
- if @events.any? - if @events.any?
%ul.bordered-list %ul.bordered-list
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
%li %li
%span.light %span.light
%i.fa.fa-clock-o %i.fa.fa-clock-o
= event.created_at.to_s(:time) = event.created_at.strftime('%-I:%M%P')
- if event.push? - if event.push?
#{event.action_name} #{event.ref_type} #{event.action_name} #{event.ref_type}
%strong %strong
...@@ -30,4 +30,4 @@ ...@@ -30,4 +30,4 @@
= event.project_name = event.project_name
- else - else
%p %p
No contributions found for #{@calendar_date.to_s(:short)} No contributions found for #{@calendar_date.to_s(:medium)}
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
.tab-content .tab-content
#activity.tab-pane #activity.tab-pane
.row-content-block.calender-block.white.second-block.hidden-xs .row-content-block.calender-block.white.second-block.hidden-xs
.user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path } } .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } }
%h4.center.light %h4.center.light
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
.user-calendar-activities .user-calendar-activities
......
---
title: Fix timezone inconsistencies in user contribution graph
merge_request: 13208
author:
...@@ -48,7 +48,7 @@ module Gitlab ...@@ -48,7 +48,7 @@ module Gitlab
end end
def starting_month def starting_month
Date.today.month Date.current.month
end end
private private
...@@ -66,12 +66,18 @@ module Gitlab ...@@ -66,12 +66,18 @@ module Gitlab
.select(:id) .select(:id)
conditions = t[:created_at].gteq(date_from.beginning_of_day) conditions = t[:created_at].gteq(date_from.beginning_of_day)
.and(t[:created_at].lteq(Date.today.end_of_day)) .and(t[:created_at].lteq(Date.current.end_of_day))
.and(t[:author_id].eq(contributor.id)) .and(t[:author_id].eq(contributor.id))
date_interval = if Gitlab::Database.postgresql?
"INTERVAL '#{Time.zone.now.utc_offset} seconds'"
else
"INTERVAL #{Time.zone.now.utc_offset} SECOND"
end
Event.reorder(nil) Event.reorder(nil)
.select(t[:project_id], t[:target_type], t[:action], 'date(created_at) AS date', 'count(id) as total_amount') .select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
.group(t[:project_id], t[:target_type], t[:action], 'date(created_at)') .group(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval})")
.where(conditions) .where(conditions)
.having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql))) .having(t[:project_id].in(Arel::Nodes::SqlLiteral.new(authed_projects.to_sql)))
end end
......
...@@ -22,12 +22,14 @@ describe Gitlab::ContributionsCalendar do ...@@ -22,12 +22,14 @@ describe Gitlab::ContributionsCalendar do
end end
end end
let(:today) { Time.now.to_date } let(:today) { Time.now.utc.to_date }
let(:yesterday) { today - 1.day }
let(:tomorrow) { today + 1.day }
let(:last_week) { today - 7.days } let(:last_week) { today - 7.days }
let(:last_year) { today - 1.year } let(:last_year) { today - 1.year }
before do before do
travel_to today travel_to Time.now.utc.end_of_day
end end
after do after do
...@@ -38,7 +40,7 @@ describe Gitlab::ContributionsCalendar do ...@@ -38,7 +40,7 @@ describe Gitlab::ContributionsCalendar do
described_class.new(contributor, current_user) described_class.new(contributor, current_user)
end end
def create_event(project, day) def create_event(project, day, hour = 0)
@targets ||= {} @targets ||= {}
@targets[project] ||= create(:issue, project: project, author: contributor) @targets[project] ||= create(:issue, project: project, author: contributor)
...@@ -47,7 +49,7 @@ describe Gitlab::ContributionsCalendar do ...@@ -47,7 +49,7 @@ describe Gitlab::ContributionsCalendar do
action: Event::CREATED, action: Event::CREATED,
target: @targets[project], target: @targets[project],
author: contributor, author: contributor,
created_at: day created_at: DateTime.new(day.year, day.month, day.day, hour)
) )
end end
...@@ -68,6 +70,34 @@ describe Gitlab::ContributionsCalendar do ...@@ -68,6 +70,34 @@ describe Gitlab::ContributionsCalendar do
expect(calendar(user).activity_dates[today]).to eq(0) expect(calendar(user).activity_dates[today]).to eq(0)
expect(calendar(contributor).activity_dates[today]).to eq(2) expect(calendar(contributor).activity_dates[today]).to eq(2)
end end
context "when events fall under different dates depending on the time zone" do
before do
create_event(public_project, today, 1)
create_event(public_project, today, 4)
create_event(public_project, today, 10)
create_event(public_project, today, 16)
create_event(public_project, today, 23)
end
it "renders correct event counts within the UTC timezone" do
Time.use_zone('UTC') do
expect(calendar.activity_dates).to eq(today => 5)
end
end
it "renders correct event counts within the Sydney timezone" do
Time.use_zone('Sydney') do
expect(calendar.activity_dates).to eq(today => 3, tomorrow => 2)
end
end
it "renders correct event counts within the US Central timezone" do
Time.use_zone('Central Time (US & Canada)') do
expect(calendar.activity_dates).to eq(yesterday => 2, today => 3)
end
end
end
end end
describe '#events_by_date' do describe '#events_by_date' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment