@@ -3,6 +3,8 @@ import { parse } from 'json2csv'
33import * as moment from 'moment'
44import { EOL } from 'os'
55
6+ import { TimetrackingValidator } from './validators'
7+
68export default class ExportUtils {
79 constructor ( ) { }
810
@@ -17,46 +19,56 @@ export default class ExportUtils {
1719 }
1820
1921 /**
20- * Format timetracking to standart format
22+ * Filter timetracking data from params
2123 */
22- public formatTimetracking ( tracking : any ) {
23- // {
24- // "start":"20190612T150556Z",
25- // "end":"20190612T170000Z",
26- // "tags":["#sideproject","Task/time warrior export"]
27- // }
28- const startDate = moment ( tracking . start )
29- const endDate = moment ( tracking . end )
30- const duration = moment . duration ( endDate . diff ( startDate ) )
31- const formatDatetime = 'DD/MM/YYYY HH:mm'
24+ public filterData ( tools : any , params : any , timetracking : Array < any > ) {
25+ return timetracking . reduce ( ( acc : Array < any > , tracking : any ) => {
26+ let valid : boolean | string = new TimetrackingValidator ( ) . isValid (
27+ tracking , params
28+ )
29+ if ( valid ) {
30+ tracking = this . formatTimetracking ( tracking )
31+ }
32+ return valid ? acc . concat ( [ tracking ] ) : acc
33+ } , [ ] )
34+ }
3235
33- const [ project , description , ...tags ] = tracking . tags
34- return {
35- start : startDate . format ( formatDatetime ) ,
36- end : endDate . format ( formatDatetime ) ,
37- duration : moment ( {
38- hour : duration . hours ( ) ,
39- minute : duration . minutes ( )
40- } ) . format ( 'HH:mm' ) ,
41- project,
42- description
43- }
36+ /**
37+ * Aggregate time tracking for stats
38+ */
39+ public aggregateData ( tools : any , filteredData : Array < any > ) {
40+ const totalDurations = filteredData . reduce ( this . aggregatePerDay , { } )
41+ const totalDurationsAsArray = Object . keys ( totalDurations ) . map (
42+ trackingDate => {
43+ const [ hours , minutes ] = totalDurations [ trackingDate ] . split ( ':' )
44+ return {
45+ date : trackingDate ,
46+ total : `${ parseInt ( hours , 10 ) } h ${ minutes } min` ,
47+ human : this . convertToManHours ( totalDurations [ trackingDate ] )
48+ }
49+ }
50+ )
51+ const total = totalDurationsAsArray . reduce ( this . totalAggregate , {
52+ total : 0 , human : 0
53+ } )
54+ return totalDurationsAsArray . concat ( [ total ] )
4455 }
4556
4657 /**
4758 * Save file on disk
4859 */
49- public saveFile ( data : Array < any > , params : any ) {
60+ public saveFile ( data : Array < any > , params : any , prefix : string ) {
5061 // #neurodecisions$2019-01-12$2019-12-12.csv
62+ const format = params . format
5163 const fromDate = params . from . replace ( / - / g, '' )
5264 const toDate = params . to . replace ( / - / g, '' )
5365 const project = params . project . replace ( / [ ^ a - z A - Z ] / g, '' )
54- const filename = `${ project } -${ fromDate } -${ toDate } . ${ params . format } `
66+ const filename = `${ project } -${ fromDate } -${ toDate } - ${ prefix } . ${ format } `
5567 let writeData = null
56- if ( params . format === 'json' ) {
68+ if ( format === 'json' ) {
5769 writeData = this . formatAsJson ( data )
58- } else if ( params . format === 'csv' ) {
59- writeData = this . formatAsCsv ( data )
70+ } else if ( format === 'csv' ) {
71+ writeData = this . formatAsCsv ( data , prefix )
6072 }
6173 try {
6274 fs . writeFileSync ( filename , writeData )
@@ -78,6 +90,32 @@ export default class ExportUtils {
7890 return `${ title } ${ EOL } ${ details } `
7991 }
8092
93+ /**
94+ * Format timetracking to standart format
95+ */
96+ private formatTimetracking ( tracking : any ) {
97+ // {
98+ // "start":"20190612T150556Z",
99+ // "end":"20190612T170000Z",
100+ // "tags":["#sideproject","Task/time warrior export"]
101+ // }
102+ const startDate = moment ( tracking . start )
103+ const endDate = moment ( tracking . end )
104+ const duration = moment . duration ( endDate . diff ( startDate ) )
105+ const formatDatetime = 'DD/MM/YYYY HH:mm'
106+
107+ const [ project , description , ...tags ] = tracking . tags
108+ return {
109+ description,
110+ start : startDate . format ( formatDatetime ) ,
111+ end : endDate . format ( formatDatetime ) ,
112+ duration : moment ( {
113+ hour : duration . hours ( ) ,
114+ minute : duration . minutes ( )
115+ } ) . format ( 'HH:mm' )
116+ }
117+ }
118+
81119 /**
82120 * Stringify data
83121 */
@@ -88,12 +126,71 @@ export default class ExportUtils {
88126 /**
89127 * Convert JSON as CSV
90128 */
91- private formatAsCsv ( data ) {
92- const headers = [ 'start' , 'end' , 'duration' , 'project' , 'description' ]
129+ private formatAsCsv ( data , prefix ) {
130+ let headers = null
131+ if ( prefix === 'raw' ) {
132+ headers = [ 'description' , 'start' , 'end' , 'duration' ]
133+ } else {
134+ headers = [ 'date' , 'total' , 'human' ]
135+ }
93136 try {
94137 return parse ( data , { fields : headers } )
95138 } catch ( err ) {
96139 // FIXME handle error (err)
97140 }
98141 }
142+
143+ /**
144+ * Aggregate time tracking per day
145+ */
146+ private aggregatePerDay ( acc : any , tracking : any ) {
147+ const startDate = moment ( tracking . start , 'DD/MM/YYYY HH:mm' )
148+ const formattedStartDate = startDate . format ( 'DD/MM/YYYY' )
149+ if ( formattedStartDate in acc ) {
150+ const duration = moment . duration ( acc [ formattedStartDate ] ) . add (
151+ moment . duration ( tracking . duration )
152+ )
153+ acc [ formattedStartDate ] = moment ( {
154+ hour : duration . hours ( ) ,
155+ minute : duration . minutes ( )
156+ } ) . format ( 'HH:mm' )
157+ } else {
158+ acc [ formattedStartDate ] = tracking . duration
159+ }
160+ return acc
161+ }
162+
163+ /**
164+ * Convert duration in hours to man hours
165+ * 0,25 -> 2h
166+ * 0,50 -> 4h (half day)
167+ * 0,75 -> 6h
168+ * 1,00 -> 8h (full day)
169+ */
170+ private convertToManHours ( duration : string ) {
171+ const decimalHours = moment . duration ( duration ) . asHours ( )
172+ const rawManHours = ( decimalHours * 0.25 ) / 2
173+ let roundedManHours = 0
174+ if ( rawManHours <= 0.25 ) {
175+ roundedManHours = 0.25
176+ } else if ( rawManHours > 0.25 && rawManHours <= 0.5 ) {
177+ roundedManHours = 0.5
178+ } else if ( rawManHours > 0.5 && rawManHours <= 0.75 ) {
179+ roundedManHours = 0.75
180+ } else if ( rawManHours > 0.75 ) {
181+ roundedManHours = 1
182+ }
183+ return roundedManHours
184+ }
185+
186+ /**
187+ * Total aggregate of all time tracking
188+ */
189+ private totalAggregate ( acc : any , trackingStats : any ) {
190+ // FIXME issue with total
191+ return {
192+ total : acc . total ,
193+ human : acc . human + trackingStats . human
194+ }
195+ }
99196}
0 commit comments