github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/web/src/AnalyticsNudge.tsx (about) 1 import React, { Component } from "react" 2 import "./AnalyticsNudge.scss" 3 import { linkToTiltDocs, TiltDocsPage } from "./constants" 4 5 export const NUDGE_TIMEOUT_MS = 15000 6 const nudgeElem = (): JSX.Element => { 7 return ( 8 <p> 9 Welcome to Tilt! We collect anonymized usage data to help us improve. Is 10 that OK? ( 11 <a 12 href={linkToTiltDocs(TiltDocsPage.TelemetryFaq)} 13 target="_blank" 14 rel="noopener noreferrer" 15 > 16 Read more 17 </a> 18 ) 19 </p> 20 ) 21 } 22 const successOptInElem = (): JSX.Element => { 23 return ( 24 <p data-testid="option-success"> 25 Thanks for helping us improve Tilt for you and everyone! (You can change 26 your mind by running <code>tilt analytics opt out</code> in your 27 terminal.) 28 </p> 29 ) 30 } 31 const successOptOutElem = (): JSX.Element => { 32 return ( 33 <p data-testid="optout-success"> 34 Okay, opting you out of telemetry. If you change your mind, you can run{" "} 35 <code>tilt analytics opt in</code> in your terminal. 36 </p> 37 ) 38 } 39 const errorElem = (respBody: string): JSX.Element => { 40 return ( 41 <div role="alert"> 42 <p>Oh no, something went wrong! Request failed with:</p> 43 <pre>{respBody}</pre> 44 <p> 45 <a 46 href="https://tilt.dev/contact" 47 target="_blank" 48 rel="noopener noreferrer" 49 > 50 contact us 51 </a> 52 </p> 53 </div> 54 ) 55 } 56 57 type AnalyticsNudgeProps = { 58 needsNudge: boolean 59 } 60 61 type AnalyticsNudgeState = { 62 requestMade: boolean 63 optIn: boolean 64 responseCode: number 65 responseBody: string 66 dismissed: boolean 67 dismissTimeout: NodeJS.Timeout | null 68 } 69 70 class AnalyticsNudge extends Component< 71 AnalyticsNudgeProps, 72 AnalyticsNudgeState 73 > { 74 constructor(props: AnalyticsNudgeProps) { 75 super(props) 76 77 this.state = { 78 requestMade: false, 79 optIn: false, 80 responseCode: 0, 81 responseBody: "", 82 dismissed: false, 83 dismissTimeout: null, 84 } 85 } 86 87 componentWillUnmount() { 88 if (this.state.dismissTimeout !== null) { 89 clearTimeout(this.state.dismissTimeout) 90 } 91 } 92 93 shouldShow(): boolean { 94 return ( 95 (this.props.needsNudge && !this.state.dismissed) || 96 (!this.state.dismissed && this.state.requestMade) 97 ) 98 } 99 100 analyticsOpt(optIn: boolean) { 101 let url = `//${window.location.host}/api/analytics_opt` 102 103 let payload = { opt: optIn ? "opt-in" : "opt-out" } 104 105 this.setState({ requestMade: true, optIn: optIn }) 106 107 fetch(url, { 108 method: "post", 109 body: JSON.stringify(payload), 110 }).then((response: Response) => { 111 response.text().then((body: string) => { 112 this.setState({ 113 responseCode: response.status, 114 responseBody: body, 115 }) 116 }) 117 118 if (response.status === 200) { 119 // if we successfully recorded the choice, dismiss the nudge after a few seconds 120 const dismissTimeout = setTimeout(() => { 121 this.setState({ dismissed: true, dismissTimeout: null }) 122 }, NUDGE_TIMEOUT_MS) 123 124 this.setState({ dismissTimeout }) 125 } 126 }) 127 } 128 129 dismiss() { 130 this.setState({ dismissed: true }) 131 } 132 133 messageElem(): JSX.Element { 134 if (this.state.responseCode) { 135 if (this.state.responseCode === 200) { 136 // Successfully called opt endpt. 137 if (this.state.optIn) { 138 // User opted in 139 return ( 140 <> 141 {successOptInElem()} 142 <span> 143 <button 144 className="AnalyticsNudge-button" 145 onClick={() => this.dismiss()} 146 > 147 Dismiss 148 </button> 149 </span> 150 </> 151 ) 152 } 153 // User opted out 154 return ( 155 <> 156 {successOptOutElem()} 157 <span> 158 <button 159 className="AnalyticsNudge-button" 160 onClick={() => this.dismiss()} 161 > 162 Dismiss 163 </button> 164 </span> 165 </> 166 ) 167 } else { 168 return ( 169 // Error calling the opt endpt. 170 <> 171 {errorElem(this.state.responseBody)} 172 <span> 173 <button 174 className="AnalyticsNudge-button" 175 onClick={() => this.dismiss()} 176 > 177 Dismiss 178 </button> 179 </span> 180 </> 181 ) 182 } 183 } 184 185 if (this.state.requestMade) { 186 // Request in progress 187 return <p data-testid="opt-loading">Okay, making it so...</p> 188 } 189 return ( 190 <> 191 {nudgeElem()} 192 <span> 193 <button 194 className="AnalyticsNudge-button" 195 onClick={() => this.analyticsOpt(false)} 196 > 197 Nope 198 </button> 199 <button 200 className="AnalyticsNudge-button opt-in" 201 onClick={() => this.analyticsOpt(true)} 202 > 203 I'm in 204 </button> 205 </span> 206 </> 207 ) 208 } 209 render() { 210 if (!this.shouldShow()) { 211 return null 212 } 213 214 return ( 215 <aside 216 aria-label="Tilt analytics options" 217 className="AnalyticsNudge is-visible" 218 > 219 {this.messageElem()} 220 </aside> 221 ) 222 } 223 } 224 225 export default AnalyticsNudge