mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[#47862] Support timezones in the Query#timestamps relative date keywords.
https://community.openproject.org/work_packages/47862
This commit is contained in:
committed by
Oliver Günther
parent
09ed751fbf
commit
c193387b5c
@@ -33,7 +33,11 @@ class Timestamp
|
||||
|
||||
class TimestampParser
|
||||
DATE_KEYWORD_REGEX =
|
||||
/^(?:oneDayAgo|lastWorkingDay|oneWeekAgo|oneMonthAgo)@(?:([0-1]?[0-9]|2[0-3]):[0-5]?[0-9])$/
|
||||
%r{
|
||||
^(?:oneDayAgo|lastWorkingDay|oneWeekAgo|oneMonthAgo) # match the relative date keyword
|
||||
@(?:([0-1]?[0-9]|2[0-3]):[0-5]?[0-9]) # match the hour part
|
||||
[+-](?:([0-1]?[0-9]|2[0-3]):[0-5]?[0-9])$ # match the timezone offset
|
||||
}x
|
||||
|
||||
def initialize(string)
|
||||
@original_string = string
|
||||
@@ -163,8 +167,8 @@ class Timestamp
|
||||
date = case relative_date_keyword
|
||||
when 'oneDayAgo' then 1.day.ago
|
||||
when 'lastWorkingDay' then Day.last_working.date || 1.day.ago
|
||||
when 'oneWeekAgo' then 1.week.ago
|
||||
when 'oneMonthAgo' then 1.month.ago
|
||||
when 'oneWeekAgo' then 1.week.ago
|
||||
when 'oneMonthAgo' then 1.month.ago
|
||||
end
|
||||
|
||||
Time.zone.parse(time_part, date)
|
||||
|
||||
@@ -78,8 +78,10 @@ get:
|
||||
description: >
|
||||
Indicates the timestamps to filter by when showing changed attributes
|
||||
on work packages. Values can be either ISO8601 dates, ISO8601 durations and the
|
||||
following relative date keywords: "oneDayAgo@HH:MM", "lastWorkingDay@HH:MM", "oneWeekAgo@HH:MM",
|
||||
"oneMonthAgo@HH:MM". The "HH:MM" part represents the zero paded hours and minutes.
|
||||
following relative date keywords: "oneDayAgo@HH:MM+HH:MM", "lastWorkingDay@HH:MM+HH:MM",
|
||||
"oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM".
|
||||
The first "HH:MM" part represents the zero paded hours and minutes.
|
||||
The last "+HH:MM" part represents the timezone offset from UTC associated with the time.
|
||||
example: '2023-01-01,P-1Y,PT0S,lastWorkingDay@12:00'
|
||||
in: query
|
||||
required: false
|
||||
|
||||
@@ -70,8 +70,12 @@ get:
|
||||
description: >
|
||||
Indicates the timestamps to filter by when showing changed attributes
|
||||
on work packages. Values can be either ISO8601 dates, ISO8601 durations and the
|
||||
following relative date keywords: "oneDayAgo@HH:MM", "lastWorkingDay@HH:MM", "oneWeekAgo@HH:MM",
|
||||
"oneMonthAgo@HH:MM". The "HH:MM" part represents the zero paded hours and minutes.
|
||||
following relative date keywords: "oneDayAgo@HH:MM+HH:MM", "lastWorkingDay@HH:MM+HH:MM",
|
||||
"oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM".
|
||||
The first "HH:MM" part represents the zero paded hours and minutes.
|
||||
The last "+HH:MM" part represents the timezone offset from UTC associated with the time,
|
||||
the offset can be positive or negative e.g."oneDayAgo@01:00+01:00", "oneDayAgo@01:00-01:00".
|
||||
|
||||
example: '2023-01-01,P-1Y,PT0S,lastWorkingDay@12:00'
|
||||
in: query
|
||||
required: false
|
||||
|
||||
@@ -138,8 +138,12 @@ get:
|
||||
description: >
|
||||
Indicates the timestamps to filter by when showing changed attributes
|
||||
on work packages. Values can be either ISO8601 dates, ISO8601 durations and the
|
||||
following relative date keywords: "oneDayAgo@HH:MM", "lastWorkingDay@HH:MM", "oneWeekAgo@HH:MM",
|
||||
"oneMonthAgo@HH:MM". The "HH:MM" part represents the zero paded hours and minutes.
|
||||
following relative date keywords: "oneDayAgo@HH:MM+HH:MM", "lastWorkingDay@HH:MM+HH:MM",
|
||||
"oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM".
|
||||
The first "HH:MM" part represents the zero paded hours and minutes.
|
||||
The last "+HH:MM" part represents the timezone offset from UTC associated with the time,
|
||||
the offset can be positive or negative e.g."oneDayAgo@01:00+01:00", "oneDayAgo@01:00-01:00".
|
||||
|
||||
example: '2023-01-01,P-1Y,PT0S,lastWorkingDay@12:00'
|
||||
in: query
|
||||
required: false
|
||||
|
||||
@@ -74,8 +74,12 @@ get:
|
||||
- description: |-
|
||||
In order to perform a [baseline comparison](/docs/api/baseline-comparisons) of the work-package attributes, you may
|
||||
provide one or several timestamps in ISO-8601 format as comma-separated list. The timestamps may be absolute or relative,
|
||||
such as ISO8601 dates, ISO8601 durations and the following relative date keywords: "oneDayAgo@HH:MM", "lastWorkingDay@HH:MM",
|
||||
"oneWeekAgo@HH:MM", "oneMonthAgo@HH:MM". The "HH:MM" part represents the zero paded hours and minutes.
|
||||
such as ISO8601 dates, ISO8601 durations and the following relative date keywords: "oneDayAgo@HH:MM+HH:MM",
|
||||
"lastWorkingDay@HH:MM+HH:MM", "oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM".
|
||||
The first "HH:MM" part represents the zero paded hours and minutes.
|
||||
The last "+HH:MM" part represents the timezone offset from UTC associated with the time,
|
||||
the offset can be positive or negative e.g."oneDayAgo@01:00+01:00", "oneDayAgo@01:00-01:00".
|
||||
|
||||
Usually, the first timestamp is the baseline date, the last timestamp is the current date.
|
||||
in: query
|
||||
name: timestamps
|
||||
|
||||
@@ -127,8 +127,12 @@ get:
|
||||
- description: |-
|
||||
In order to perform a [baseline comparison](/docs/api/baseline-comparisons), you may provide one or several timestamps
|
||||
in ISO-8601 format as comma-separated list. The timestamps may be absolute or relative,
|
||||
such as ISO8601 dates, ISO8601 durations and the following relative date keywords: "oneDayAgo@HH:MM", "lastWorkingDay@HH:MM",
|
||||
"oneWeekAgo@HH:MM", "oneMonthAgo@HH:MM". The "HH:MM" part represents the zero paded hours and minutes.
|
||||
such as ISO8601 dates, ISO8601 durations and the following relative date keywords: "oneDayAgo@HH:MM+HH:MM",
|
||||
"lastWorkingDay@HH:MM+HH:MM", "oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM".
|
||||
The first "HH:MM" part represents the zero paded hours and minutes.
|
||||
The last "+HH:MM" part represents the timezone offset from UTC associated with the time,
|
||||
the offset can be positive or negative e.g."oneDayAgo@01:00+01:00", "oneDayAgo@01:00-01:00".
|
||||
|
||||
Usually, the first timestamp is the baseline date, the last timestamp is the current date.
|
||||
example: '2022-01-01T00:00:00Z,PT0S'
|
||||
in: query
|
||||
|
||||
@@ -22,7 +22,8 @@ description: |-
|
||||
```
|
||||
|
||||
Each timestamp should be given as an [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) string, either an absolute date and time with timezone, e.g. `"2022-01-01T00:00:00Z"`, or a relative timestamp utilizing the [ISO-8601-Duration](https://en.wikipedia.org/wiki/ISO_8601#Durations) format, e.g. `"P-1Y"`, which is composed of an initial `"P"` for "Period", and a duration. `"P-1Y"` is interpreted as the relative timestamp "1 year ago".
|
||||
Furthermore, a set of predefined relative date keywords can also be passed for the timestamps: `"oneDayAgo@HH:MM", "lastWorkingDay@HH:MM", "oneWeekAgo@HH:MM", "oneMonthAgo@HH:MM"`. The `"HH:MM"` part represents the zero paded hours and minutes, e.g. `"oneMonthAgo@21:20"`.
|
||||
Furthermore, a set of predefined relative date keywords can also be passed for the timestamps: `"oneDayAgo@HH:MM+HH:MM", "lastWorkingDay@HH:MM+HH:MM", "oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM"`. The `"HH:MM"` part represents the zero paded hours and minutes, e.g. `"oneMonthAgo@21:00+00:00"`. The last "+HH:MM" part represents the timezone offset from UTC associated with the time,
|
||||
e.g. `"oneMonthAgo@21:00+02:00"` means a +2 hour timezone offset from UTC. The offset can be positive or negative e.g."oneDayAgo@01:00+01:00", "oneDayAgo@01:00-01:00".
|
||||
|
||||
Several timestamps should be passed as comma-separated list of these ISO-8601 strings to the `timestamps` parameter, e.g. `"2022-01-01T00:00:00Z,PT0S"`.
|
||||
|
||||
|
||||
@@ -237,8 +237,10 @@ description: |-
|
||||
|
||||
* `Timestamp` - refers to an ISO 8601 combined date and time, e.g. "2014-05-21T13:37:00Z" or to an ISO 8601 duration, e.g. "P1DT18H".
|
||||
The following shorthand values are also being parsed as duration: "1d", "1w", "1m", "1y", "1y-2m", "-2y".
|
||||
It can also refer the following relative date keywords: "oneDayAgo@HH:MM", "lastWorkingDay@HH:MM", "oneWeekAgo@HH:MM", "oneMonthAgo@HH:MM".
|
||||
The "HH:MM" part represents the zero paded hours and minutes of the relative date e.g. "oneDayAgo@00:00", "lastWorkingDay@21:00".
|
||||
It can also refer the following relative date keywords: `"oneDayAgo@HH:MM+HH:MM", "lastWorkingDay@HH:MM+HH:MM", "oneWeekAgo@HH:MM+HH:MM", "oneMonthAgo@HH:MM+HH:MM"`.
|
||||
The `"HH:MM"` part represents the zero paded hours and minutes, e.g. `"oneMonthAgo@21:00+00:00"`. The last "+HH:MM" part represents the timezone offset from UTC
|
||||
associated with the time, e.g. `"oneMonthAgo@21:00+02:00"` means a +2 hour timezone offset from UTC.
|
||||
The offset can be positive or negative e.g."oneDayAgo@01:00+01:00", "oneDayAgo@01:00-01:00".
|
||||
|
||||
## Colors
|
||||
|
||||
|
||||
@@ -111,8 +111,8 @@ export class TimezoneService {
|
||||
return date.diff(today, 'days');
|
||||
}
|
||||
|
||||
public formattedTime(datetimeString:string):string {
|
||||
return this.parseDatetime(datetimeString).format(this.getTimeFormat());
|
||||
public formattedTime(datetimeString:string, format?:string):string {
|
||||
return this.parseDatetime(datetimeString).format(format || this.getTimeFormat());
|
||||
}
|
||||
|
||||
public formattedDatetime(datetimeString:string):string {
|
||||
|
||||
+1
-3
@@ -52,9 +52,7 @@
|
||||
</spot-tooltip>
|
||||
|
||||
|
||||
<span slot="description"
|
||||
*ngIf="timeZoneSelected"
|
||||
[textContent]="text.time_description()"></span>
|
||||
<span slot="description" [textContent]="text.time_description()"></span>
|
||||
</spot-form-field>
|
||||
|
||||
<div class="spot-action-bar"
|
||||
|
||||
+7
-12
@@ -76,12 +76,10 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit {
|
||||
|
||||
public selectedFilter = '-';
|
||||
|
||||
public selectedTimezoneFormattedTime = '';
|
||||
public selectedTimezoneFormattedTime = `${this.selectedTime}+00:00`;
|
||||
|
||||
public filterSelected = false;
|
||||
|
||||
public timeZoneSelected = false;
|
||||
|
||||
public daysNumber = 0;
|
||||
|
||||
public tooltipPosition = SpotDropAlignmentOption.TopRight;
|
||||
@@ -141,17 +139,18 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit {
|
||||
public ngOnInit():void {
|
||||
if (this.wpTableBaseline.isActive()) {
|
||||
const value = this.wpTableBaseline.current[0];
|
||||
const [date, time] = value.split('@');
|
||||
const [date, timeWithZone] = value.split('@');
|
||||
const time = timeWithZone.split(/[+-]/)[0];
|
||||
|
||||
this.filterChange(date);
|
||||
this.selectedTime = time || '00:00';
|
||||
this.selectedTimezoneFormattedTime = timeWithZone || '00:00+00:00';
|
||||
this.filterSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public clearSelection():void {
|
||||
this.filterSelected = false;
|
||||
this.timeZoneSelected = false;
|
||||
this.selectedTime = '0:00';
|
||||
this.selectedDate = '';
|
||||
this.selectedFilter = '-';
|
||||
@@ -167,7 +166,7 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit {
|
||||
if (this.selectedFilter === '-') {
|
||||
this.wpTableBaseline.disable();
|
||||
} else {
|
||||
const filterString = `${this.selectedFilter}@${this.selectedTime}`;
|
||||
const filterString = `${this.selectedFilter}@${this.selectedTimezoneFormattedTime}`;
|
||||
this.wpTableBaseline.update([filterString, DEFAULT_TIMESTAMP]);
|
||||
}
|
||||
|
||||
@@ -236,12 +235,8 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit {
|
||||
|
||||
public timeChange(value:string):void {
|
||||
this.selectedTime = value;
|
||||
const timeZone = this.configuration.isTimezoneSet();
|
||||
if (timeZone) {
|
||||
this.timeZoneSelected = true;
|
||||
const dateTime= `${this.selectedDate} ${value}`;
|
||||
this.selectedTimezoneFormattedTime = this.timezoneService.formattedTime(dateTime);
|
||||
}
|
||||
const dateTime= `${this.selectedDate} ${value}`;
|
||||
this.selectedTimezoneFormattedTime = this.timezoneService.formattedTime(dateTime, 'HH:mmZ');
|
||||
}
|
||||
|
||||
public filterChange(value:string):void {
|
||||
|
||||
@@ -65,7 +65,7 @@ describe 'baseline query saving', js: true do
|
||||
wp_table.expect_and_dismiss_toaster(message: 'Successful creation.')
|
||||
|
||||
query = retry_block { Query.find_by! name: 'Baseline query' }
|
||||
expect(query.timestamps).to eq ['oneDayAgo@00:00', 'PT0S']
|
||||
expect(query.timestamps).to eq ['oneDayAgo@00:00+00:00', 'PT0S']
|
||||
|
||||
wp_table.visit_query query
|
||||
|
||||
|
||||
+139
-24
@@ -178,19 +178,67 @@ describe Timestamp do
|
||||
|
||||
describe "when providing relative date keywords" do
|
||||
describe "oneDayAgo@12:00" do
|
||||
subject { described_class.parse("oneDayAgo@12:00") }
|
||||
subject { described_class.parse("oneDayAgo@12:00+00:00") }
|
||||
|
||||
it "returns a Timestamp representing the yesterday at 12:00 pm" do
|
||||
it "returns a Timestamp representing the yesterday at 12:00 pm +00:00" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 1.day.ago.change(hour: 12)
|
||||
expect(subject.to_time).to eq 1.day.ago.utc.change(hour: 12)
|
||||
end
|
||||
|
||||
context "with a timezone difference of +02:00" do
|
||||
subject { described_class.parse("oneDayAgo@12:00+02:00") }
|
||||
|
||||
it "returns a Timestamp representing the yesterday at 10:00 pm UTC" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 1.day.ago.utc.change(hour: 10)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a timezone difference of -02:00" do
|
||||
subject { described_class.parse("oneDayAgo@12:00-02:00") }
|
||||
|
||||
it "returns a Timestamp representing the yesterday at 14:00 pm UTC" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 1.day.ago.utc.change(hour: 14)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a timezone rolling over to next day" do
|
||||
subject { described_class.parse("oneDayAgo@23:00-02:00") }
|
||||
|
||||
it "returns a Timestamp representing today at 1:00 am UTC" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq Time.now.utc.change(hour: 1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a timezone rolling back to previous day" do
|
||||
subject { described_class.parse("oneDayAgo@00:00+02:00") }
|
||||
|
||||
it "returns a Timestamp representing the 2 days ago at 22:00 pm UTC" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 2.days.ago.utc.change(hour: 22)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "lastWorkingDay@12:00" do
|
||||
subject { described_class.parse("lastWorkingDay@12:00") }
|
||||
describe "lastWorkingDay@12:00+00:00" do
|
||||
subject { described_class.parse("lastWorkingDay@12:00+00:00") }
|
||||
|
||||
before do
|
||||
week_with_all_days_working
|
||||
@@ -202,31 +250,31 @@ describe Timestamp do
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 2.days.ago.change(hour: 12)
|
||||
expect(subject.to_time).to eq 2.days.ago.utc.change(hour: 12)
|
||||
end
|
||||
end
|
||||
|
||||
describe "oneWeekAgo@12:00" do
|
||||
subject { described_class.parse("oneWeekAgo@12:00") }
|
||||
describe "oneWeekAgo@12:00+00:00" do
|
||||
subject { described_class.parse("oneWeekAgo@12:00+00:00") }
|
||||
|
||||
it "returns a Timestamp representing the last week at 12:00 pm" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 1.week.ago.change(hour: 12)
|
||||
expect(subject.to_time).to eq 1.week.ago.utc.change(hour: 12)
|
||||
end
|
||||
end
|
||||
|
||||
describe "oneMonthAgo@12:00" do
|
||||
subject { described_class.parse("oneMonthAgo@12:00") }
|
||||
describe "oneMonthAgo@00:00+00:00" do
|
||||
subject { described_class.parse("oneMonthAgo@00:00+00:00") }
|
||||
|
||||
it "returns a Timestamp representing the last month at 12:00 pm" do
|
||||
it "returns a Timestamp representing the last month at 00:00 am" do
|
||||
expect(subject).to be_a described_class
|
||||
expect(subject).to be_valid
|
||||
expect(subject).to be_relative
|
||||
expect(subject).to be_relative_date_keyword
|
||||
expect(subject.to_time).to eq 1.month.ago.change(hour: 12)
|
||||
expect(subject.to_time).to eq 1.month.ago.utc.change(hour: 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -248,18 +296,85 @@ describe Timestamp do
|
||||
end
|
||||
|
||||
describe "when providing something invalid with relative date keywords" do
|
||||
subject { described_class.parse("oneDayAgo@") }
|
||||
context "with missing the hours part" do
|
||||
subject { described_class.parse("oneDayAgo@") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when providing something invalid with relative date keywords#2" do
|
||||
subject { described_class.parse("oneDayAgo@11:22:asd") }
|
||||
context "with having an invalid hours part" do
|
||||
subject { described_class.parse("oneDayAgo@11:22:asd") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having an invalid timezone part" do
|
||||
subject { described_class.parse("oneDayAgo@11:22+00:0a") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having more than 23 in the hours part" do
|
||||
subject { described_class.parse("oneDayAgo@24:22+00:00") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having more than 59 in the minutes part" do
|
||||
subject { described_class.parse("oneDayAgo@23:60+00:00") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having more than 23 in the time zone offset hours part" do
|
||||
subject { described_class.parse("oneDayAgo@00:00+24:00") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having more than 59 in the time zone offset minutes part" do
|
||||
subject { described_class.parse("oneDayAgo@00:00+00:60") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having a negative hours part" do
|
||||
subject { described_class.parse("oneDayAgo@-23:00+00:00") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
context "with having a negative minutes part" do
|
||||
subject { described_class.parse("oneDayAgo@00:-50+00:00") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
# Negtive time zone offsets are allowed
|
||||
context "with having a negative time zone offset minutes part" do
|
||||
subject { described_class.parse("oneDayAgo@00:00+00:-50") }
|
||||
|
||||
it "raises an error" do
|
||||
expect { subject }.to raise_error ArgumentError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -345,7 +460,7 @@ describe Timestamp do
|
||||
end
|
||||
|
||||
describe "for a timestamp as a date keyword representing a point in time relative to now" do
|
||||
let(:timestamp) { described_class.new("oneWeekAgo@12:00") }
|
||||
let(:timestamp) { described_class.new("oneWeekAgo@12:00+00:00") }
|
||||
|
||||
it "returns true" do
|
||||
expect(subject).to be true
|
||||
@@ -374,10 +489,10 @@ describe Timestamp do
|
||||
end
|
||||
|
||||
describe "for a timestamp as a date keyword representing a point in time relative to now" do
|
||||
let(:timestamp) { described_class.new('oneDayAgo@12:00') }
|
||||
let(:timestamp) { described_class.new('oneDayAgo@12:00+02:00') }
|
||||
|
||||
it "returns an relative date keyword" do
|
||||
expect(subject).to eq 'oneDayAgo@12:00'
|
||||
expect(subject).to eq 'oneDayAgo@12:00+02:00'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -326,7 +326,7 @@ describe "POST /api/v3/queries/form", with_flag: { show_changes: true } do
|
||||
|
||||
describe 'with all parameters given' do
|
||||
let(:status) { create(:status) }
|
||||
let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00', "P0D"] }
|
||||
let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00+00:00', "P0D"] }
|
||||
|
||||
let(:parameters) do
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ describe "POST /api/v3/queries", with_flag: { show_changes: true } do
|
||||
shared_let(:user) { create(:admin) }
|
||||
shared_let(:status) { create(:status) }
|
||||
shared_let(:project) { create(:project) }
|
||||
shared_let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00', "P0D"] }
|
||||
shared_let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00+00:00', "P0D"] }
|
||||
|
||||
let(:default_params) do
|
||||
{
|
||||
|
||||
@@ -35,7 +35,7 @@ describe "POST /api/v3/queries/form", with_flag: { show_changes: true } do
|
||||
let(:user) { create(:admin) }
|
||||
let(:role) { create(:existing_role, permissions:) }
|
||||
let(:permissions) { %i(view_work_packages manage_public_queries) }
|
||||
let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00', "P0D"] }
|
||||
let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00+00:00', "P0D"] }
|
||||
|
||||
let!(:project) { create(:project_with_types, members: { user => role }) }
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ describe "PATCH /api/v3/queries/:id" do
|
||||
shared_let(:user) { create(:admin) }
|
||||
shared_let(:status) { create(:status) }
|
||||
shared_let(:project) { create(:project) }
|
||||
shared_let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00', "P0D"] }
|
||||
shared_let(:timestamps) { [1.week.ago.iso8601, 'lastWorkingDay@12:00+00:00', "P0D"] }
|
||||
|
||||
let!(:query) do
|
||||
create(
|
||||
|
||||
@@ -244,8 +244,9 @@ describe 'API v3 Work package resource',
|
||||
last_response
|
||||
end
|
||||
|
||||
let(:path) { "#{api_v3_paths.work_packages}?timestamps=#{timestamps.join(',')}" }
|
||||
let(:timestamps) { [Timestamp.parse('2015-01-01T00:00:00Z'), Timestamp.now] }
|
||||
let(:timestamps_param) { CGI.escape(timestamps.join(',')) }
|
||||
let(:path) { "#{api_v3_paths.work_packages}?timestamps=#{timestamps_param}" }
|
||||
let(:baseline_time) { timestamps.first.to_time }
|
||||
let(:created_at) { baseline_time - 1.day }
|
||||
|
||||
@@ -355,7 +356,7 @@ describe 'API v3 Work package resource',
|
||||
end
|
||||
|
||||
describe "when filtering such that the filters do not match at all timestamps" do
|
||||
let(:path) { "#{api_v3_paths.path_for(:work_packages, filters:)}×tamps=#{timestamps.join(',')}" }
|
||||
let(:path) { "#{api_v3_paths.path_for(:work_packages, filters:)}×tamps=#{timestamps_param}" }
|
||||
let(:filters) do
|
||||
[
|
||||
{
|
||||
@@ -774,7 +775,7 @@ describe 'API v3 Work package resource',
|
||||
end
|
||||
|
||||
context 'when the timestamps are relative date keywords' do
|
||||
let(:timestamps) { [Timestamp.parse('oneWeekAgo@11:00'), Timestamp.parse('lastWorkingDay@12:00')] }
|
||||
let(:timestamps) { [Timestamp.parse('oneWeekAgo@11:00+00:00'), Timestamp.parse('lastWorkingDay@12:00+00:00')] }
|
||||
|
||||
it 'has an embedded link to the baseline work package' do
|
||||
expect(subject.body)
|
||||
@@ -801,13 +802,13 @@ describe 'API v3 Work package resource',
|
||||
|
||||
it 'has the relative timestamps within the _meta timestamps' do
|
||||
expect(subject.body)
|
||||
.to be_json_eql('oneWeekAgo@11:00'.to_json)
|
||||
.to be_json_eql('oneWeekAgo@11:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_embedded/attributesByTimestamp/0/_meta/timestamp')
|
||||
expect(subject.body)
|
||||
.to be_json_eql('lastWorkingDay@12:00'.to_json)
|
||||
.to be_json_eql('lastWorkingDay@12:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_embedded/attributesByTimestamp/1/_meta/timestamp')
|
||||
expect(subject.body)
|
||||
.to be_json_eql('lastWorkingDay@12:00'.to_json)
|
||||
.to be_json_eql('lastWorkingDay@12:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_meta/timestamp')
|
||||
end
|
||||
|
||||
@@ -820,7 +821,7 @@ describe 'API v3 Work package resource',
|
||||
describe "timestamp" do
|
||||
it 'has the baseline timestamp, which is the first timestmap' do
|
||||
expect(subject.body)
|
||||
.to be_json_eql('oneWeekAgo@11:00'.to_json)
|
||||
.to be_json_eql('oneWeekAgo@11:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_embedded/attributesByTimestamp/0/_meta/timestamp')
|
||||
end
|
||||
end
|
||||
@@ -832,7 +833,7 @@ describe 'API v3 Work package resource',
|
||||
describe "timestamp" do
|
||||
it 'has the current timestamp, which is the second timestamp' do
|
||||
expect(subject.body)
|
||||
.to be_json_eql('lastWorkingDay@12:00'.to_json)
|
||||
.to be_json_eql('lastWorkingDay@12:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_embedded/attributesByTimestamp/1/_meta/timestamp')
|
||||
end
|
||||
end
|
||||
@@ -842,7 +843,7 @@ describe 'API v3 Work package resource',
|
||||
end
|
||||
|
||||
describe "when the work package has not changed at all between the baseline and today" do
|
||||
let(:timestamps) { [Timestamp.parse('lastWorkingDay@12:00'), Timestamp.now] }
|
||||
let(:timestamps) { [Timestamp.parse('lastWorkingDay@12:00+00:00'), Timestamp.now] }
|
||||
|
||||
describe "_meta" do
|
||||
describe "timestamp" do
|
||||
@@ -868,7 +869,7 @@ describe 'API v3 Work package resource',
|
||||
it 'has the baseline timestamp, which is the first timestamp, ' \
|
||||
'in the same format as given in the request parameter' do
|
||||
expect(subject.body)
|
||||
.to be_json_eql('lastWorkingDay@12:00'.to_json)
|
||||
.to be_json_eql('lastWorkingDay@12:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_embedded/attributesByTimestamp/0/_meta/timestamp')
|
||||
end
|
||||
end
|
||||
@@ -889,7 +890,7 @@ describe 'API v3 Work package resource',
|
||||
# before the baseline date, because the baseline date is relative to the
|
||||
# current date. This means that the filter will become outdated and we cannot
|
||||
# use a cached result in this case.
|
||||
let(:path) { "#{api_v3_paths.path_for(:work_packages, filters:)}×tamps=#{timestamps.join(',')}" }
|
||||
let(:path) { "#{api_v3_paths.path_for(:work_packages, filters:)}×tamps=#{timestamps_param}" }
|
||||
let(:filters) do
|
||||
[
|
||||
{
|
||||
@@ -930,7 +931,7 @@ describe 'API v3 Work package resource',
|
||||
end
|
||||
|
||||
context "with relative date keyword timestamps" do
|
||||
let(:timestamps) { [Timestamp.parse('oneWeekAgo@12:00'), Timestamp.now] }
|
||||
let(:timestamps) { [Timestamp.parse('oneWeekAgo@12:00+00:00'), Timestamp.now] }
|
||||
let(:created_at) { '2015-01-01' }
|
||||
|
||||
describe "when the filter becomes outdated" do
|
||||
@@ -940,7 +941,7 @@ describe 'API v3 Work package resource',
|
||||
# current date. This means that the filter will become outdated and we cannot
|
||||
# use a cached result in this case.
|
||||
|
||||
let(:path) { "#{api_v3_paths.path_for(:work_packages, filters:)}×tamps=#{timestamps.join(',')}" }
|
||||
let(:path) { "#{api_v3_paths.path_for(:work_packages, filters:)}×tamps=#{timestamps_param}" }
|
||||
let(:filters) do
|
||||
[
|
||||
{
|
||||
@@ -954,10 +955,10 @@ describe 'API v3 Work package resource',
|
||||
let(:search_term) { 'original' }
|
||||
|
||||
it 'has the relative timestamps within the _meta timestamps' do
|
||||
expect(timestamps.first.to_s).to eq('oneWeekAgo@12:00')
|
||||
expect(timestamps.first.to_s).to eq('oneWeekAgo@12:00+00:00')
|
||||
expect(timestamps.first).to be_relative
|
||||
expect(subject.body)
|
||||
.to be_json_eql('oneWeekAgo@12:00'.to_json)
|
||||
.to be_json_eql('oneWeekAgo@12:00+00:00'.to_json)
|
||||
.at_path('_embedded/elements/0/_embedded/attributesByTimestamp/0/_meta/timestamp')
|
||||
expect(subject.body)
|
||||
.to be_json_eql('PT0S'.to_json)
|
||||
|
||||
@@ -208,7 +208,8 @@ describe 'API v3 Work package resource',
|
||||
end
|
||||
|
||||
describe 'GET /api/v3/work_packages/:id?timestamps=' do
|
||||
let(:get_path) { "#{api_v3_paths.work_package(work_package.id)}?timestamps=#{timestamps.map(&:to_s).join(',')}" }
|
||||
let(:timestamps_param) { CGI.escape(timestamps.map(&:to_s).join(',')) }
|
||||
let(:get_path) { "#{api_v3_paths.work_package(work_package.id)}?timestamps=#{timestamps_param}" }
|
||||
|
||||
describe 'response body' do
|
||||
subject do
|
||||
@@ -420,7 +421,7 @@ describe 'API v3 Work package resource',
|
||||
end
|
||||
|
||||
context 'when the timestamps are relative date keywords' do
|
||||
let(:timestamps) { [Timestamp.new('oneWeekAgo@12:00'), Timestamp.now] }
|
||||
let(:timestamps) { [Timestamp.new('oneWeekAgo@12:00+00:00'), Timestamp.now] }
|
||||
|
||||
it 'has an embedded link to the baseline work package' do
|
||||
expect(subject)
|
||||
@@ -441,7 +442,7 @@ describe 'API v3 Work package resource',
|
||||
describe 'timestamp' do
|
||||
it 'has the relative timestamps' do
|
||||
expect(subject)
|
||||
.to be_json_eql('oneWeekAgo@12:00'.to_json)
|
||||
.to be_json_eql('oneWeekAgo@12:00+00:00'.to_json)
|
||||
.at_path('_embedded/attributesByTimestamp/0/_meta/timestamp')
|
||||
expect(subject)
|
||||
.to be_json_eql('PT0S'.to_json)
|
||||
@@ -455,7 +456,7 @@ describe 'API v3 Work package resource',
|
||||
before { login_as current_user }
|
||||
|
||||
context "with relative timestamps" do
|
||||
let(:timestamps) { [Timestamp.parse("oneDayAgo@00:00"), Timestamp.now] }
|
||||
let(:timestamps) { [Timestamp.parse("oneDayAgo@00:00+00:00"), Timestamp.now] }
|
||||
let(:created_at) { '2015-01-01' }
|
||||
|
||||
describe "attributesByTimestamp" do
|
||||
@@ -490,7 +491,7 @@ describe 'API v3 Work package resource',
|
||||
|
||||
describe "_meta" do
|
||||
describe "exists" do
|
||||
let(:timestamps) { [Timestamp.parse("oneDayAgo@00:00")] }
|
||||
let(:timestamps) { [Timestamp.parse("oneDayAgo@00:00+00:00")] }
|
||||
let(:created_at) { 25.hours.ago }
|
||||
|
||||
it "is not cached" do
|
||||
|
||||
@@ -342,13 +342,13 @@ describe API::V3::ParseQueryParamsService,
|
||||
end
|
||||
|
||||
it_behaves_like 'transforms' do
|
||||
let(:params) { { timestamps: "oneMonthAgo@11:00, now" } }
|
||||
let(:expected) { { timestamps: [Timestamp.parse("oneMonthAgo@11:00"), Timestamp.parse("P-0Y")] } }
|
||||
let(:params) { { timestamps: "oneMonthAgo@11:00+00:00, now" } }
|
||||
let(:expected) { { timestamps: [Timestamp.parse("oneMonthAgo@11:00+00:00"), Timestamp.parse("P-0Y")] } }
|
||||
end
|
||||
|
||||
it_behaves_like 'transforms' do
|
||||
let(:params) { { timestamps: "oneMonthAgo@11:00, oneWeekAgo@12:00" } }
|
||||
let(:expected) { { timestamps: [Timestamp.parse("oneMonthAgo@11:00"), Timestamp.parse("oneWeekAgo@12:00")] } }
|
||||
let(:params) { { timestamps: "oneMonthAgo@11:00+00:00, oneWeekAgo@12:00+10:00" } }
|
||||
let(:expected) { { timestamps: [Timestamp.parse("oneMonthAgo@11:00+00:00"), Timestamp.parse("oneWeekAgo@12:00+10:00")] } }
|
||||
end
|
||||
|
||||
describe "for invalid parameters" do
|
||||
|
||||
@@ -236,7 +236,7 @@ describe API::V3::WorkPackageCollectionFromQueryService,
|
||||
end
|
||||
|
||||
context 'when timestamps are given', with_flag: { show_changes: true } do
|
||||
let(:timestamps) { [Timestamp.parse("P-1Y"), Timestamp.parse("oneWeekAgo@12:00"), Timestamp.now] }
|
||||
let(:timestamps) { [Timestamp.parse("P-1Y"), Timestamp.parse("oneWeekAgo@12:00+00:00"), Timestamp.now] }
|
||||
let(:query) { build_stubbed(:query, timestamps:) }
|
||||
|
||||
it 'has the query timestamps' do
|
||||
|
||||
@@ -195,7 +195,7 @@ describe UpdateQueryFromParamsService,
|
||||
let(:timestamps) do
|
||||
[
|
||||
Timestamp.parse("2022-10-29T23:01:23Z"),
|
||||
Timestamp.parse("oneWeekAgo@12:00"),
|
||||
Timestamp.parse("oneWeekAgo@12:00+00:00"),
|
||||
Timestamp.parse("PT0S")
|
||||
]
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user