44var  defaultScript  =  "" ; 
55
66window . addEventListener ( "load" ,  function ( )  { 
7-     sendMetric ( "/page-load" ) ; 
87    measureViewport ( ) ; 
8+     // Retained so we can compare with GA. 
9+     sendLegacyPageViewMetric ( ) ; 
10+ 
911    // Capture the default script loaded in the editor 
1012    defaultScript  =  EDITOR . getCode ( ) ; 
1113
@@ -27,214 +29,202 @@ function attachActionListeners() {
2729    $ ( ".action" ) . on ( "click" ,  actionClickListener ) ; 
2830} 
2931
30- function  sendMetric ( slug )  { 
31-     slug  =  slug . replace ( / , / g,  '-' ) ; 
32-     // Do not send the metrics if running locally during development 
33-     if  ( ( location . hostname  ===  "localhost"  ||  location . hostname  ===  "127.0.0.1"  || 
34-             location . hostname  ===  "" )  && 
35-             // Check this is not Puppeteer, the tests need to intercept the sent requests 
36-             ( typeof  navigator  !==  "undefined"  &&  ! navigator . webdriver ) )  { 
37-         console . log ( "metric: "  +  slug ) ; 
38-     }  else  { 
32+ function  sendLegacyPageViewMetric ( )  { 
33+     if  ( location . hostname  !==  "localhost"  &&  location . hostname  !==  "127.0.0.1"  && 
34+     location . hostname  !==  "" )  { 
3935        $ . ajax ( { 
4036            type : "GET" , 
41-             url : "https://metrics.microbit.org/pyeditor-"  +  EDITOR_VERSION  +  slug , 
37+             url : "https://metrics.microbit.org/pyeditor-"  +  EDITOR_VERSION  +  "/page-load" , 
4238            complete : function ( res )  { 
4339                // Do nothing 
4440            } 
4541        } ) ; 
4642    } 
43+     else  { 
44+         console . log ( "metric: pageview" ) ;  
45+     } 
46+ } 
47+ 
48+ function  sendEvent ( action ,  label ,  value )  { 
49+     // Do not send the metrics if running locally during development 
50+     if  ( location . hostname  ===  "localhost"  ||  location . hostname  ===  "127.0.0.1"  || 
51+             location . hostname  ===  "" )  {  
52+         console . log ( "metric: "  +  action  +  " "  +  label  +  " "  +  value ) ; 
53+     }  else  { 
54+         gtag ( 'event' ,  action ,  { 
55+             event_category : 'Python Editor '  +  EDITOR_VERSION , 
56+             event_label : label , 
57+             value : value 
58+         } ) ; 
59+     } 
4760} 
4861
4962function  measureViewport ( ) { 
50-   var   widthRange  =  [ [ 0 ,  480 ] ,  [ 481 ,  890 ] ,  [ 891 , 1024 ] ,  [ 1025 ,  1280 ] ,  [ 1281 ,  10000 ] ] ; 
51-   var  viewportWidth  =  $ ( window ) . width ( ) ; 
63+      var   widthRange  =  [ [ 0 ,  480 ] ,  [ 481 ,  890 ] ,  [ 891 , 1024 ] ,  [ 1025 ,  1280 ] ,  [ 1281 ,  10000 ] ] ; 
64+      var  viewportWidth  =  $ ( window ) . width ( ) ; 
5265
53-   var  bucket  =  widthRange . filter ( function ( a )  { 
54-     if  ( viewportWidth  >=  a [ 0 ]  &&  viewportWidth  <=  a [ 1 ] )  return  a ; 
55-   } ) ; 
66+     var  bucket  =  widthRange . filter ( function ( a )  { 
67+         if  ( viewportWidth  >=  a [ 0 ]  &&  viewportWidth  <=  a [ 1 ] )  return  a ; 
68+     } ) ; 
69+     var  label  =  bucket . toString ( ) . replace ( / , / g,  '-' ) ; 
5670
57-   var  slug  =  "/width/"  +  bucket [ 0 ] . toString ( ) ; 
58-   sendMetric ( slug ) ; 
71+     sendEvent ( 'viewport' ,  label ,  1 ) ; 
5972} 
6073
6174function  trackLines ( )  { 
6275    var  range  =  [ [ 0 ,  20 ] ,  [ 21 ,  50 ] ,  [ 51 ,  100 ] ,  [ 101 ,  200 ] ,  [ 201 ,  500 ] ,  [ 501 ,  1000 ] ,  [ 1001 ,  1000000 ] ] ; 
6376    var  currentCode  =  EDITOR . getCode ( ) ; 
64-     var  slug  =  "/lines/" ; 
6577
6678    if  ( currentCode  ==  defaultScript )  { 
67-       slug   +=   " default-script" ; 
79+          var   label   =   ' default' ; 
6880    }  else  { 
69-       var  lines  =  currentCode . split ( / \r \n | \r | \n / ) . length ; 
70-       var  bucket  =  range . filter ( function ( a )  { 
71-           if  ( lines  >=  a [ 0 ]  &&  lines  <=  a [ 1 ] )  return  a ; 
72-       } ) ; 
73-       slug   + =  bucket [ 0 ] . toString ( ) ; 
81+          var  lines  =  currentCode . split ( / \r \n | \r | \n / ) . length ; 
82+          var  bucket  =  range . filter ( function ( a )  { 
83+              if  ( lines  >=  a [ 0 ]  &&  lines  <=  a [ 1 ] )  return  a ; 
84+          } ) ; 
85+          var   label   =  bucket . toString ( ) . replace ( / , / g ,   '-' ) ; 
7486    } 
7587
76-     sendMetric ( slug ) ; 
88+     sendEvent ( 'lines' ,   label ,   1 ) ; 
7789} 
7890
7991function  trackFiles ( )  { 
8092    var  range  =  [ [ 11 ,  15 ] ,  [ 16 ,  20 ] ,  [ 21 ,  25 ] ,  [ 26 ,  1000 ] ] ; 
81-     var  files ; 
82- 
83-     try  { 
84-         // Will always be at least 1 due to main.py 
85-         files  =  micropythonFs . ls ( ) . length ; 
86-     } 
87-     catch ( e )  { 
88-         // If the filesystem is not present assume one file (main.py) 
89-         sendMetric ( "/files/1" ) ; 
90-         return ; 
91-     } 
93+     var  files  =  micropythonFs . ls ( ) . length ; 
9294
95+     var  label  =  files . toString ( ) ; 
9396    if  ( files  >  10 )  { 
9497        var  bucket  =  range . filter ( function ( a )  { 
9598            if  ( files  >=  a [ 0 ]  &&  files  <=  a [ 1 ] )  return  a ; 
9699        } ) ; 
97-         var  slug  =  "/files/"  +  bucket [ 0 ] . toString ( ) ; 
98-         sendMetric ( slug ) ; 
99-     } 
100-     else  { 
101-         var  slug  =  "/files/"  +  files . toString ( ) ; 
102-         sendMetric ( slug ) ; 
100+         label  =  bucket . toString ( ) . replace ( / , / g,  '-' ) ; 
103101    } 
102+ 
103+     sendEvent ( 'files' ,  label  ,  1 ) ; 
104+ } 
105+ 
106+ /** 
107+  * Returns an analytics label for the file extension. 
108+  * "none" is used when there is no extension. 
109+  */ 
110+ function  fileExtension ( file )  { 
111+   var  lowerName  =  file . name . toLowerCase ( ) ; 
112+   var  ext  =  ( / [ . ] / . exec ( lowerName ) )  ? / [ ^ . ] + $ / . exec ( lowerName )  : [ "none" ] ; 
113+   return  ext [ 0 ] ; 
104114} 
105115
106116// Dropping into editor 
107117$ ( '#editor' ) . on ( 'drop' ,  function  ( e )  { 
108118    var  file  =  e . originalEvent . dataTransfer . files [ 0 ] ; 
109-     var  ext  =  ( / [ . ] / . exec ( file . name ) )  ? / [ ^ . ] + $ / . exec ( file . name )  : [ "none" ] ; 
110- 
111-     switch ( ext [ 0 ] )  { 
112-       case  "py" :
113-         sendMetric ( "/drop/py" ) ; 
114-         break ; 
115-       case  "hex" :
116-         sendMetric ( "/drop/hex" ) ; 
117-         break ; 
118-       default :
119-         sendMetric ( "/drop/error/invalid" ) ; 
119+     var  label  =  fileExtension ( file ) ; 
120+     if  ( ( label  ===  'py' )  ||  ( label === 'hex' ) )  { 
121+         sendEvent ( 'load' ,  'drop-editor-'  +  label ,  1 ) ; 
122+     }  else  { 
123+         sendEvent ( 'load' ,  'error-drop-editor-type-'  +  label ,  1 ) ; 
120124    } 
121125} ) ; 
122126
123127// Dropping into load area 
124128document . addEventListener ( 'load-drop' ,  function  ( e )  { 
125129    var  file  =  e . detail ; 
126-     var  ext  =  ( / [ . ] / . exec ( file . name ) )  ? / [ ^ . ] + $ / . exec ( file . name )  : [ "none" ] ; 
127- 
128-     switch ( ext [ 0 ] )  { 
129-       case  "py" :
130-         sendMetric ( "/drop/py" ) ; 
131-         break ; 
132-       case  "hex" :
133-         sendMetric ( "/drop/hex" ) ; 
134-         break ; 
135-       default :
136-         sendMetric ( "/drop/error/invalid" ) ; 
130+     var  label  =  fileExtension ( file ) ; 
131+     if  ( ( label  ===  'py' )  ||  ( label === 'hex' ) )  { 
132+         sendEvent ( 'load' ,  'drop-load-'  +  label ,  1 ) ; 
133+     }  else  { 
134+         sendEvent ( 'load' ,  'error-drop-load-type-'  +  label ,  1 ) ; 
137135    } 
138136} ) ; 
139137
140138// Uploading a file to the editor via Load/Save modal 
141139document . addEventListener ( 'file-upload' ,  function  ( e )  { 
142140    var  files  =  e . detail ; 
143141    if  ( files . length  ===  1 )  { 
144-         var  f  =  files [ 0 ] ; 
145-         var  ext  =  ( / [ . ] / . exec ( f . name ) )  ? / [ ^ . ] + $ / . exec ( f . name )  : null ; 
146-         switch ( ext [ 0 ] )  { 
147-             case  "py" :
148-               sendMetric ( "/file-upload/py" ) ; 
149-               break ; 
150-             case  "hex" :
151-               sendMetric ( "/file-upload/hex" ) ; 
152-               break ; 
153-             default :
154-               sendMetric ( "/file-upload/error/invalid" ) ; 
155-           } 
142+         var  label  =  fileExtension ( files [ 0 ] ) ; 
143+         if  ( ( label  ===  'py' )  ||  ( label === 'hex' ) )  { 
144+             sendEvent ( 'load' ,  'file-upload-'  +  label ,  1 ) ; 
145+         }  else  { 
146+             sendEvent ( 'load' ,  'error-file-upload-type-'  +  label ,  1 ) ; 
147+         } 
156148    }  else  { 
157-         sendMetric ( "/ file-upload/error/multiple-files" ) ; 
149+         sendEvent ( 'load' ,   'error- file-upload-multiple' ,   1 ) ; 
158150    } 
159151} ) ; 
160152
161- // WebUSB Error  
153+ // WebUSB flash time and errors  
162154document . addEventListener ( 'webusb' ,  function  ( e )  { 
163155    var  details  =  e . detail ; 
164- 
165-     // Put flash time into brackets 
166-     if (  details [ "event-type" ]  ==  "flash-time"  )  { 
167- 
156+     if  ( details [ "event-type" ]  ==  "flash-time"  )  { 
157+         var  flashAction  =  'WebUSB-time' ; 
168158        var  flashTime  =  details [ "message" ] ; 
169-         var  timeBracket ; 
170- 
159+         var  flashLabel  =  'unknown' ; 
171160        if  ( flashTime  <  2000 )  { 
172-           timeBracket  =  "0-2" ; 
173-         } 
174-         else  if  ( flashTime  <=  4000 )  { 
175-           timeBracket  =  "2-4" ; 
176-         } 
177-         else  if  ( flashTime  <=  6000 )  { 
178-           timeBracket  =  "4-6" ; 
179-         } 
180-         else  if  ( flashTime  <=  10000 )  { 
181-           timeBracket  =  "6-10" ; 
182-         } 
183-         else  if  ( flashTime  <=  20000 )  { 
184-           timeBracket  =  "10-20" ; 
161+             flashLabel  =  "0-2" ; 
162+         }  else  if  ( flashTime  <=  4000 )  { 
163+             flashLabel  =  "2-4" ; 
164+         }  else  if  ( flashTime  <=  6000 )  { 
165+             flashLabel  =  "4-6" ; 
166+         }  else  if  ( flashTime  <=  10000 )  { 
167+             flashLabel  =  "6-10" ; 
168+         }  else  if  ( flashTime  <=  20000 )  { 
169+             flashLabel  =  "10-20" ; 
170+         }  else  if  ( flashTime  <=  30000 )  { 
171+             flashLabel  =  "20-30" ; 
172+         }  else  if  ( flashTime  <=  60000 )  { 
173+             flashLabel  =  "30-60" ; 
174+         }  else  if  ( flashTime  <=  120000 )  { 
175+             flashLabel  =  "60-120" ; 
176+         }  else  { 
177+             flashLabel  =  "120+" ; 
185178        } 
186-         else  if  ( flashTime  <=  30000 )  { 
187-           timeBracket  =  "20-30" ; 
188-         } 
189-         else  if  ( flashTime  <=  60000 )  { 
190-           timeBracket  =  "30-60" ; 
191-         } 
192-         else  if  ( flashTime  <=  120000 )  { 
193-           timeBracket  =  "60-120" ; 
194-         } 
195-         else  { 
196-           timeBracket  =  "120+" ; 
197-         } 
198- 
199-         // Set message to time bracket 
200-         details [ "message" ]  =  timeBracket ; 
201- 
202-     }  
203- 
204-     sendMetric ( '/webusb/'  +  details [ "flash-type" ]  +  '/'  +  details [ "event-type" ]  +  "/"  +  details [ "message" ] ) ; 
179+         var  flashValue  =  1 ; 
180+     }  else  if  ( details [ "event-type" ]  ==  "info"  )  { 
181+         var  flashAction  =  'WebUSB-info' ; 
182+         var  flashLabel  =  details [ "message" ] ; 
183+         var  flashValue  =  1 ; 
184+     }  else  if  ( details [ "event-type" ]  ==  "error"  )  { 
185+         var  flashAction  =  'WebUSB-error' ; 
186+         // TODO: At the moment details["flash-type"] only indicates the flash 
187+         // option selected and doesn't distinguish full flash fall-back 
188+         // so we won't include it for now, but that could be changed 
189+         var  flashLabel  =  details [ "message" ] ; 
190+         var  flashValue  =  1 ; 
191+     }  else  { 
192+         var  flashAction  =  'WebUSB-error' ; 
193+         var  flashLabel  =  "unknown-event/"  +  details [ "event-type" ]  +  "/"  +  details [ "message" ] ; 
194+         var  flashValue  =  1 ; 
195+     } 
196+     sendEvent ( flashAction ,  flashLabel ,  flashValue ) ; 
205197} ) ; 
206198
199+ // Any click on an element with the "action" class is captured here 
207200function  actionClickListener ( e )  { 
208-     var  slug ; 
209-     if ( e . target )  { 
210-         slug  =  "/action/"  +  $ ( e . target ) . closest ( ".action" ) [ 0 ] . id ; 
211-     }  else  { 
212-         slug  =  "/action/" ; 
201+     var  actionId  =  'unknown' ; 
202+     if  ( e . target )  { 
203+         actionId  =  $ ( e . target ) . closest ( ".action" ) [ 0 ] . id ; 
204+         actionId  =  actionId . replace ( "command-" ,  "" ) ; 
213205    } 
214206
215-     slug  =  slug . replace ( "command-" ,  "" ) ; 
216- 
217-     if  ( slug . match ( / _ s a v e / ) )  { 
218-       slug  =  "/action/fs-file-save" ; 
207+     if  ( actionId . match ( / _ s a v e / ) )  { 
208+       actionId  =  "file-save" ; 
219209    } 
220-     else  if  ( slug . match ( / _ r e m o v e / ) )  { 
221-       slug  =  "/action/fs- file-remove" ; 
210+     else  if  ( actionId . match ( / _ r e m o v e / ) )  { 
211+       actionId  =  "file-remove" ; 
222212    } 
223-     else  if  ( slug . match ( / f l a s h i n g - o v e r l a y - d o w n l o a d / ) )  { 
224-       slug  =  "/action/partial-flashing/ error-link- download" ; 
213+     else  if  ( actionId . match ( / f l a s h i n g - o v e r l a y - d o w n l o a d / ) )  { 
214+       actionId  =  "webusb/ error-modal/ download-hex " ; 
225215    } 
226-     else  if  ( slug . match ( / f l a s h i n g - o v e r l a y - t r o u b l e s h o o t / ) )  { 
227-       slug  =  "/action/partial-flashing/ error-link- troubleshoot" ; 
216+     else  if  ( actionId . match ( / f l a s h i n g - o v e r l a y - t r o u b l e s h o o t / ) )  { 
217+       actionId  =  "webusb/ error-modal/ troubleshoot" ; 
228218    } 
229219
230-     switch ( slug )  { 
231-       case  "/action/ flash" :
232-       case  "/action/ download" :
220+     switch ( actionId )  { 
221+       case  "flash" :
222+       case  "download" :
233223        trackFiles ( ) ; 
234224        trackLines ( ) ; 
235225        /* Intentional fall-through */ 
236226      default :
237-         sendMetric ( slug ) ; 
238-          break ; 
227+         sendEvent ( 'click' ,   actionId ,   1 ) ; 
228+       break ; 
239229    } 
240230} 
0 commit comments