Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
03b7df7b
Commit
03b7df7b
authored
Sep 05, 2017
by
Bryce Johnson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Lightly refactor prettyTime module.
parent
a3b74f1a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
156 additions
and
255 deletions
+156
-255
app/assets/javascripts/lib/utils/pretty_time.js
app/assets/javascripts/lib/utils/pretty_time.js
+50
-57
app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js
...ripts/sidebar/components/time_tracking/collapsed_state.js
+2
-3
app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
...ripts/sidebar/components/time_tracking/comparison_pane.js
+4
-13
spec/javascripts/pretty_time_spec.js
spec/javascripts/pretty_time_spec.js
+100
-182
No files found.
app/assets/javascripts/lib/utils/pretty_time.js
View file @
03b7df7b
import
_
from
'
underscore
'
;
(()
=>
{
/*
* TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* non-condensed, abbreviateTimelengths)
* */
const
utils
=
window
.
gl
.
utils
=
gl
.
utils
||
{};
const
prettyTime
=
utils
.
prettyTime
=
{
/*
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day
* or week length.
*/
parseSeconds
(
seconds
,
{
daysPerWeek
=
5
,
hoursPerDay
=
8
}
=
{})
{
const
DAYS_PER_WEEK
=
daysPerWeek
;
const
HOURS_PER_DAY
=
hoursPerDay
;
const
MINUTES_PER_HOUR
=
60
;
const
MINUTES_PER_WEEK
=
DAYS_PER_WEEK
*
HOURS_PER_DAY
*
MINUTES_PER_HOUR
;
const
MINUTES_PER_DAY
=
HOURS_PER_DAY
*
MINUTES_PER_HOUR
;
const
timePeriodConstraints
=
{
weeks
:
MINUTES_PER_WEEK
,
days
:
MINUTES_PER_DAY
,
hours
:
MINUTES_PER_HOUR
,
minutes
:
1
,
};
/*
* TODO: Make these methods more configurable (e.g. stringifyTime condensed or
* non-condensed, abbreviateTimelengths)
* */
/*
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
* Seconds can be negative or positive, zero or non-zero. Can be configured for any day
* or week length.
*/
export
function
parseSeconds
(
seconds
,
{
daysPerWeek
=
5
,
hoursPerDay
=
8
}
=
{})
{
const
DAYS_PER_WEEK
=
daysPerWeek
;
const
HOURS_PER_DAY
=
hoursPerDay
;
const
MINUTES_PER_HOUR
=
60
;
const
MINUTES_PER_WEEK
=
DAYS_PER_WEEK
*
HOURS_PER_DAY
*
MINUTES_PER_HOUR
;
const
MINUTES_PER_DAY
=
HOURS_PER_DAY
*
MINUTES_PER_HOUR
;
const
timePeriodConstraints
=
{
weeks
:
MINUTES_PER_WEEK
,
days
:
MINUTES_PER_DAY
,
hours
:
MINUTES_PER_HOUR
,
minutes
:
1
,
};
let
unorderedMinutes
=
prettyTime
.
secondsToMinutes
(
seconds
);
let
unorderedMinutes
=
Math
.
abs
(
seconds
/
MINUTES_PER_HOUR
);
return
_
.
mapObject
(
timePeriodConstraints
,
(
minutesPerPeriod
)
=>
{
const
periodCount
=
Math
.
floor
(
unorderedMinutes
/
minutesPerPeriod
);
return
_
.
mapObject
(
timePeriodConstraints
,
(
minutesPerPeriod
)
=>
{
const
periodCount
=
Math
.
floor
(
unorderedMinutes
/
minutesPerPeriod
);
unorderedMinutes
-=
(
periodCount
*
minutesPerPeriod
);
unorderedMinutes
-=
(
periodCount
*
minutesPerPeriod
);
return
periodCount
;
});
},
return
periodCount
;
});
}
/*
* Accepts a timeObject
and returns a condensed string representation of it
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
*/
/*
* Accepts a timeObject (see parseSeconds)
and returns a condensed string representation of it
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
*/
stringifyTime
(
timeObject
)
{
const
reducedTime
=
_
.
reduce
(
timeObject
,
(
memo
,
unitValue
,
unitName
)
=>
{
const
isNonZero
=
!!
unitValue
;
return
isNonZero
?
`
${
memo
}
${
unitValue
}${
unitName
.
charAt
(
0
)}
`
:
memo
;
},
''
).
trim
();
return
reducedTime
.
length
?
reducedTime
:
'
0m
'
;
},
export
function
stringifyTime
(
timeObject
)
{
const
reducedTime
=
_
.
reduce
(
timeObject
,
(
memo
,
unitValue
,
unitName
)
=>
{
const
isNonZero
=
!!
unitValue
;
return
isNonZero
?
`
${
memo
}
${
unitValue
}${
unitName
.
charAt
(
0
)}
`
:
memo
;
},
''
).
trim
();
return
reducedTime
.
length
?
reducedTime
:
'
0m
'
;
}
/*
* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
* the first non-zero unit/value pair.
*/
/*
* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
* the first non-zero unit/value pair.
*/
abbreviateTime
(
timeStr
)
{
return
timeStr
.
split
(
'
'
)
.
filter
(
unitStr
=>
unitStr
.
charAt
(
0
)
!==
'
0
'
)[
0
];
},
export
function
abbreviateTime
(
timeStr
)
{
return
timeStr
.
split
(
'
'
)
.
filter
(
unitStr
=>
unitStr
.
charAt
(
0
)
!==
'
0
'
)[
0
];
}
secondsToMinutes
(
seconds
)
{
return
Math
.
abs
(
seconds
/
60
);
},
};
})(
window
.
gl
||
(
window
.
gl
=
{}));
app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.js
View file @
03b7df7b
import
stopwatchSvg
from
'
icons/_icon_stopwatch.svg
'
;
import
'
../../../lib/utils/pretty_time
'
;
import
{
abbreviateTime
}
from
'
../../../lib/utils/pretty_time
'
;
export
default
{
name
:
'
time-tracking-collapsed-state
'
,
...
...
@@ -79,7 +78,7 @@ export default {
},
methods
:
{
abbreviateTime
(
timeStr
)
{
return
gl
.
utils
.
prettyTime
.
abbreviateTime
(
timeStr
);
return
abbreviateTime
(
timeStr
);
},
},
template
:
`
...
...
app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.js
View file @
03b7df7b
import
'
../../../lib/utils/pretty_time
'
;
const
prettyTime
=
gl
.
utils
.
prettyTime
;
import
{
parseSeconds
,
stringifyTime
}
from
'
../../../lib/utils/pretty_time
'
;
export
default
{
name
:
'
time-tracking-comparison-pane
'
,
...
...
@@ -23,12 +21,12 @@ export default {
},
},
computed
:
{
parsedRemaining
()
{
parsed
Time
Remaining
()
{
const
diffSeconds
=
this
.
timeEstimate
-
this
.
timeSpent
;
return
p
rettyTime
.
p
arseSeconds
(
diffSeconds
);
return
parseSeconds
(
diffSeconds
);
},
timeRemainingHumanReadable
()
{
return
prettyTime
.
stringifyTime
(
this
.
parsed
Remaining
);
return
stringifyTime
(
this
.
parsedTime
Remaining
);
},
timeRemainingTooltip
()
{
const
prefix
=
this
.
timeRemainingMinutes
<
0
?
'
Over by
'
:
'
Time remaining:
'
;
...
...
@@ -44,13 +42,6 @@ export default {
timeRemainingStatusClass
()
{
return
this
.
timeEstimate
>=
this
.
timeSpent
?
'
within_estimate
'
:
'
over_estimate
'
;
},
/* Parsed time values */
parsedEstimate
()
{
return
prettyTime
.
parseSeconds
(
this
.
timeEstimate
);
},
parsedSpent
()
{
return
prettyTime
.
parseSeconds
(
this
.
timeSpent
);
},
},
template
:
`
<div class="time-tracking-comparison-pane">
...
...
spec/javascripts/pretty_time_spec.js
View file @
03b7df7b
import
'
~/lib/utils/pretty_time
'
;
import
{
parseSeconds
,
abbreviateTime
,
stringifyTime
}
from
'
~/lib/utils/pretty_time
'
;
(()
=>
{
const
prettyTime
=
gl
.
utils
.
prettyTime
;
function
assertTimeUnits
(
obj
,
minutes
,
hours
,
days
,
weeks
)
{
expect
(
obj
.
minutes
).
toBe
(
minutes
);
expect
(
obj
.
hours
).
toBe
(
hours
);
expect
(
obj
.
days
).
toBe
(
days
);
expect
(
obj
.
weeks
).
toBe
(
weeks
);
}
describe
(
'
prettyTime methods
'
,
function
()
{
describe
(
'
parseSeconds
'
,
function
()
{
it
(
'
should correctly parse a negative value
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
describe
(
'
prettyTime methods
'
,
()
=>
{
describe
(
'
parseSeconds
'
,
()
=>
{
it
(
'
should correctly parse a negative value
'
,
()
=>
{
const
zeroSeconds
=
parseSeconds
(
-
1000
)
;
const
zeroSeconds
=
parser
(
-
1000
);
expect
(
zeroSeconds
.
minutes
).
toBe
(
16
);
expect
(
zeroSeconds
.
hours
).
toBe
(
0
);
expect
(
zeroSeconds
.
days
).
toBe
(
0
);
expect
(
zeroSeconds
.
weeks
).
toBe
(
0
);
});
it
(
'
should correctly parse a zero value
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
const
zeroSeconds
=
parser
(
0
);
expect
(
zeroSeconds
.
minutes
).
toBe
(
0
);
expect
(
zeroSeconds
.
hours
).
toBe
(
0
);
expect
(
zeroSeconds
.
days
).
toBe
(
0
);
expect
(
zeroSeconds
.
weeks
).
toBe
(
0
);
});
it
(
'
should correctly parse a small non-zero second values
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
const
subOneMinute
=
parser
(
10
);
expect
(
subOneMinute
.
minutes
).
toBe
(
0
);
expect
(
subOneMinute
.
hours
).
toBe
(
0
);
expect
(
subOneMinute
.
days
).
toBe
(
0
);
expect
(
subOneMinute
.
weeks
).
toBe
(
0
);
const
aboveOneMinute
=
parser
(
100
);
expect
(
aboveOneMinute
.
minutes
).
toBe
(
1
);
expect
(
aboveOneMinute
.
hours
).
toBe
(
0
);
expect
(
aboveOneMinute
.
days
).
toBe
(
0
);
expect
(
aboveOneMinute
.
weeks
).
toBe
(
0
);
const
manyMinutes
=
parser
(
1000
);
expect
(
manyMinutes
.
minutes
).
toBe
(
16
);
expect
(
manyMinutes
.
hours
).
toBe
(
0
);
expect
(
manyMinutes
.
days
).
toBe
(
0
);
expect
(
manyMinutes
.
weeks
).
toBe
(
0
);
});
it
(
'
should correctly parse large second values
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
const
aboveOneHour
=
parser
(
4800
);
expect
(
aboveOneHour
.
minutes
).
toBe
(
20
);
expect
(
aboveOneHour
.
hours
).
toBe
(
1
);
expect
(
aboveOneHour
.
days
).
toBe
(
0
);
expect
(
aboveOneHour
.
weeks
).
toBe
(
0
);
const
aboveOneDay
=
parser
(
110000
);
expect
(
aboveOneDay
.
minutes
).
toBe
(
33
);
expect
(
aboveOneDay
.
hours
).
toBe
(
6
);
expect
(
aboveOneDay
.
days
).
toBe
(
3
);
expect
(
aboveOneDay
.
weeks
).
toBe
(
0
);
const
aboveOneWeek
=
parser
(
25000000
);
expect
(
aboveOneWeek
.
minutes
).
toBe
(
26
);
expect
(
aboveOneWeek
.
hours
).
toBe
(
0
);
expect
(
aboveOneWeek
.
days
).
toBe
(
3
);
expect
(
aboveOneWeek
.
weeks
).
toBe
(
173
);
});
assertTimeUnits
(
zeroSeconds
,
16
,
0
,
0
,
0
);
});
it
(
'
should correctly accept a custom param for hoursPerDay
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
const
config
=
{
hoursPerDay
:
24
};
it
(
'
should correctly parse a zero value
'
,
()
=>
{
const
zeroSeconds
=
parseSeconds
(
0
);
const
aboveOneHour
=
parser
(
4800
,
config
);
assertTimeUnits
(
zeroSeconds
,
0
,
0
,
0
,
0
);
});
expect
(
aboveOneHour
.
minutes
).
toBe
(
20
);
expect
(
aboveOneHour
.
hours
).
toBe
(
1
);
expect
(
aboveOneHour
.
days
).
toBe
(
0
);
expect
(
aboveOneHour
.
weeks
).
toBe
(
0
);
it
(
'
should correctly parse a small non-zero second values
'
,
()
=>
{
const
subOneMinute
=
parseSeconds
(
10
);
const
aboveOneMinute
=
parseSeconds
(
10
0
);
const
manyMinutes
=
parseSeconds
(
100
0
);
const
aboveOneDay
=
parser
(
110000
,
config
);
assertTimeUnits
(
subOneMinute
,
0
,
0
,
0
,
0
);
assertTimeUnits
(
aboveOneMinute
,
1
,
0
,
0
,
0
);
assertTimeUnits
(
manyMinutes
,
16
,
0
,
0
,
0
);
});
expect
(
aboveOneDay
.
minutes
).
toBe
(
33
);
expect
(
aboveOneDay
.
hours
).
toBe
(
6
);
expect
(
aboveOneDay
.
days
).
toBe
(
1
);
expect
(
aboveOneDay
.
weeks
).
toBe
(
0
);
it
(
'
should correctly parse large second values
'
,
()
=>
{
const
aboveOneHour
=
parseSeconds
(
4800
);
const
aboveOneDay
=
parseSeconds
(
110000
);
const
aboveOneWeek
=
parseSeconds
(
2500000
0
);
const
aboveOneWeek
=
parser
(
25000000
,
config
);
assertTimeUnits
(
aboveOneHour
,
20
,
1
,
0
,
0
);
assertTimeUnits
(
aboveOneDay
,
33
,
6
,
3
,
0
);
assertTimeUnits
(
aboveOneWeek
,
26
,
0
,
3
,
173
);
});
expect
(
aboveOneWeek
.
minutes
).
toBe
(
26
);
expect
(
aboveOneWeek
.
hours
).
toBe
(
8
);
expect
(
aboveOneWeek
.
days
).
toBe
(
4
);
it
(
'
should correctly accept a custom param for hoursPerDay
'
,
()
=>
{
const
config
=
{
hoursPerDay
:
24
};
expect
(
aboveOneWeek
.
weeks
).
toBe
(
57
);
});
const
aboveOneHour
=
parseSeconds
(
4800
,
config
);
const
aboveOneDay
=
parseSeconds
(
110000
,
config
);
const
aboveOneWeek
=
parseSeconds
(
25000000
,
config
);
it
(
'
should correctly accept a custom param for daysPerWeek
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
const
config
=
{
daysPerWeek
:
7
};
assertTimeUnits
(
aboveOneHour
,
20
,
1
,
0
,
0
);
assertTimeUnits
(
aboveOneDay
,
33
,
6
,
1
,
0
);
assertTimeUnits
(
aboveOneWeek
,
26
,
8
,
4
,
57
);
});
const
aboveOneHour
=
parser
(
4800
,
config
);
it
(
'
should correctly accept a custom param for daysPerWeek
'
,
()
=>
{
const
config
=
{
daysPerWeek
:
7
};
expect
(
aboveOneHour
.
minutes
).
toBe
(
20
);
expect
(
aboveOneHour
.
hours
).
toBe
(
1
);
expect
(
aboveOneHour
.
days
).
toBe
(
0
);
expect
(
aboveOneHour
.
weeks
).
toBe
(
0
);
const
aboveOneHour
=
parseSeconds
(
4800
,
config
);
const
aboveOneDay
=
parseSeconds
(
110000
,
config
);
const
aboveOneWeek
=
parseSeconds
(
25000000
,
config
);
const
aboveOneDay
=
parser
(
110000
,
config
);
assertTimeUnits
(
aboveOneHour
,
20
,
1
,
0
,
0
);
assertTimeUnits
(
aboveOneDay
,
33
,
6
,
3
,
0
);
assertTimeUnits
(
aboveOneWeek
,
26
,
0
,
0
,
124
);
});
expect
(
aboveOneDay
.
minutes
).
toBe
(
33
);
expect
(
aboveOneDay
.
hours
).
toBe
(
6
);
expect
(
aboveOneDay
.
days
).
toBe
(
3
);
expect
(
aboveOneDay
.
weeks
).
toBe
(
0
);
it
(
'
should correctly accept custom params for daysPerWeek and hoursPerDay
'
,
()
=>
{
const
config
=
{
daysPerWeek
:
55
,
hoursPerDay
:
14
};
const
aboveOneWeek
=
parser
(
25000000
,
config
);
const
aboveOneHour
=
parseSeconds
(
4800
,
config
);
const
aboveOneDay
=
parseSeconds
(
110000
,
config
);
const
aboveOneWeek
=
parseSeconds
(
25000000
,
config
);
expect
(
aboveOneWeek
.
minutes
).
toBe
(
26
);
expect
(
aboveOneWeek
.
hours
).
toBe
(
0
);
expect
(
aboveOneWeek
.
days
).
toBe
(
0
);
assertTimeUnits
(
aboveOneHour
,
20
,
1
,
0
,
0
);
assertTimeUnits
(
aboveOneDay
,
33
,
2
,
2
,
0
);
assertTimeUnits
(
aboveOneWeek
,
26
,
0
,
1
,
9
);
});
});
expect
(
aboveOneWeek
.
weeks
).
toBe
(
124
);
});
describe
(
'
stringifyTime
'
,
()
=>
{
it
(
'
should stringify values with all non-zero units
'
,
()
=>
{
const
timeObject
=
{
weeks
:
1
,
days
:
4
,
hours
:
7
,
minutes
:
20
,
};
it
(
'
should correctly accept custom params for daysPerWeek and hoursPerDay
'
,
function
()
{
const
parser
=
prettyTime
.
parseSeconds
;
const
config
=
{
daysPerWeek
:
55
,
hoursPerDay
:
14
};
const
timeString
=
stringifyTime
(
timeObject
);
const
aboveOneHour
=
parser
(
4800
,
config
);
expect
(
timeString
).
toBe
(
'
1w 4d 7h 20m
'
);
});
expect
(
aboveOneHour
.
minutes
).
toBe
(
20
);
expect
(
aboveOneHour
.
hours
).
toBe
(
1
);
expect
(
aboveOneHour
.
days
).
toBe
(
0
);
expect
(
aboveOneHour
.
weeks
).
toBe
(
0
);
it
(
'
should stringify values with some non-zero units
'
,
()
=>
{
const
timeObject
=
{
weeks
:
0
,
days
:
4
,
hours
:
0
,
minutes
:
20
,
};
const
aboveOneDay
=
parser
(
110000
,
config
);
const
timeString
=
stringifyTime
(
timeObject
);
expect
(
aboveOneDay
.
minutes
).
toBe
(
33
);
expect
(
aboveOneDay
.
hours
).
toBe
(
2
);
expect
(
aboveOneDay
.
days
).
toBe
(
2
);
expect
(
aboveOneDay
.
weeks
).
toBe
(
0
);
expect
(
timeString
).
toBe
(
'
4d 20m
'
);
});
const
aboveOneWeek
=
parser
(
25000000
,
config
);
it
(
'
should stringify values with no non-zero units
'
,
()
=>
{
const
timeObject
=
{
weeks
:
0
,
days
:
0
,
hours
:
0
,
minutes
:
0
,
};
expect
(
aboveOneWeek
.
minutes
).
toBe
(
26
);
expect
(
aboveOneWeek
.
hours
).
toBe
(
0
);
expect
(
aboveOneWeek
.
days
).
toBe
(
1
);
const
timeString
=
stringifyTime
(
timeObject
);
expect
(
aboveOneWeek
.
weeks
).
toBe
(
9
);
});
expect
(
timeString
).
toBe
(
'
0m
'
);
});
});
describe
(
'
stringifyTime
'
,
function
()
{
it
(
'
should stringify values with all non-zero units
'
,
function
()
{
const
timeObject
=
{
weeks
:
1
,
days
:
4
,
hours
:
7
,
minutes
:
20
,
};
const
timeString
=
prettyTime
.
stringifyTime
(
timeObject
);
expect
(
timeString
).
toBe
(
'
1w 4d 7h 20m
'
);
});
it
(
'
should stringify values with some non-zero units
'
,
function
()
{
const
timeObject
=
{
weeks
:
0
,
days
:
4
,
hours
:
0
,
minutes
:
20
,
};
const
timeString
=
prettyTime
.
stringifyTime
(
timeObject
);
expect
(
timeString
).
toBe
(
'
4d 20m
'
);
});
it
(
'
should stringify values with no non-zero units
'
,
function
()
{
const
timeObject
=
{
weeks
:
0
,
days
:
0
,
hours
:
0
,
minutes
:
0
,
};
const
timeString
=
prettyTime
.
stringifyTime
(
timeObject
);
expect
(
timeString
).
toBe
(
'
0m
'
);
});
describe
(
'
abbreviateTime
'
,
()
=>
{
it
(
'
should abbreviate stringified times for weeks
'
,
()
=>
{
const
fullTimeString
=
'
1w 3d 4h 5m
'
;
expect
(
abbreviateTime
(
fullTimeString
)).
toBe
(
'
1w
'
);
});
describe
(
'
abbreviateTime
'
,
function
()
{
it
(
'
should abbreviate stringified times for weeks
'
,
function
()
{
const
fullTimeString
=
'
1w 3d 4h 5m
'
;
expect
(
prettyTime
.
abbreviateTime
(
fullTimeString
)).
toBe
(
'
1w
'
);
});
it
(
'
should abbreviate stringified times for non-weeks
'
,
function
()
{
const
fullTimeString
=
'
0w 3d 4h 5m
'
;
expect
(
prettyTime
.
abbreviateTime
(
fullTimeString
)).
toBe
(
'
3d
'
);
});
it
(
'
should abbreviate stringified times for non-weeks
'
,
()
=>
{
const
fullTimeString
=
'
0w 3d 4h 5m
'
;
expect
(
abbreviateTime
(
fullTimeString
)).
toBe
(
'
3d
'
);
});
});
})
(
window
.
gl
||
(
window
.
gl
=
{}))
;
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment