8888888b. 888888b. 888 888 "Y88b 888 "88b 888 888 888 888 .88P 888 888 888 .d88b. 888 888 8888888K. 888 .d88b. .d88b. 888 888 d8P Y8b 888 888 888 "Y88b 888 d88""88b d88P"88b 888 888 88888888 Y88 88P 888 888 888 888 888 888 888 888 .d88P Y8b. Y8bd8P 888 d88P 888 Y88..88P Y88b 888 8888888P" "Y8888 Y88P 8888888P" 888 "Y88P" "Y88888 888 Y8b d88P "Y88P"
Occam's razor should be the guiding principle of software development. To keep myself from getting lost or accidentally spending a fortune on hosting, I chose a popular and well-supported combination of front-end frameworks, to be run on AWS:
The requirements for my site were to be statically generated, so I wouldn't have to rent any servers. Some early experiments with AWS showed that the smallest instance costs $3/day. The other option is static websites, which are low cost but lack many of the features and flexibility you get with a hosted website. Choosing AWS CloudFront was a simple choice compared to GitHub Pages, as my domains are registered with Amazon. The costs can quickly spiral out of hand, so I architected this site to be very affordable.
Much of the development time went into learning these front-end frameworks. Deploying the website to AWS and setting up a deployment pipeline was comparatively simple.
There are only two Lambda functions that run on AWS at the moment: the WakaTime and Spotify integrations. Since this site is statically compiled, requesting those APIs from the browser results in a CORS error. These calls normally happen server-side, not from the browser. Microservices are a perfect application for this situation.
1import json, requests
2def lambda_handler(event, context):
3 a=requests.get('https://wakatime.com/share/@HIDDEN-LINK.json').json()
4 b=requests.get('https://wakatime.com/share/@HIDDEN-LINK.json').json()
5 return {
6 'statusCode': 200,
7 'body': json.dumps({'languages':a,'code':b})
8 }
This gets around the CORS restrictions, while maintaining the low cost architecture that I have been pursuing.
The WakaTime API is great and really easy to use. You can create sharable links that return full images or JSON data for your usage. The service itself has plugins for most editors and browsers. The WakaTime rules are amazing; let's look at some:
The standard WakaTime plugins easily recognize languages, files, projects, etc., but when you try to track web browsing, there is no inherent information from the site. I whitelisted quite a few sites that I only use for development. They get sent to Waka as files in the form of "https://aws.amazon.com". These rules are set up to convert relevant sites to a language so it can easily be picked up by the other WakaTime services.
The component that took the most configuration was the Charts. The library "react-chartjs-2" is a derivative of Chartjs, This posed a lot of problems getting the configurations correct.
Developing a functional and good looking chart component was a difficult task since I really had to use all the features of typescript and the library to make it work correctly.
1const histogramOptions = (
2 propTitle?:string,
3 propYTitle?:string,
4 propXTitle?:string,
5 propShowTooltip=true,
6 propShowLegend=true,ygrid=false,
7 xgrid=true,ydur=0,xdur=0,yaxis=true,xaxis=true) => {
8
9 const { state } = useContext(ThemeContext);
10 if(propShowTooltip==null || propShowTooltip==undefined) propShowTooltip = true;
11 let swapData = false;
12 let optionsObj:any = {
13 responsive:true,
14 animation:{
15 y: {
16 duration:ydur,
17 easing:'linear',
18 from:2000,
19 },
20 x: {
21 duration:xdur,
22 easing: 'linear',
23 from: (ctx:any) => {
24 swapData = !swapData
25 return swapData?0:2000;
26 },
27 },
28 },
29 interaction:{
30 intersect: false,
31 mode: 'nearest',
32 axis: 'xy',
33 },
34 plugins:{
35 legend:{
36 display:propShowLegend,
37 labels:{
38 color:state.useLight?'black':'white',
39 },
40 },
41 title:{
42 display:!(propTitle=='undefined' || propTitle==null),
43 text:propTitle,
44 color:state.useLight?'black':'white',
45 padding:15,
46 font:{
47 size:20,
48 family:'mono',
49 weight:'bold'
50 },
51 },
52 tooltip:{
53 enabled:propShowTooltip,
54 }
55 },
56 scales:{
57 y:{
58 display:yaxis,
59 min:0,
60 title:{
61 display:!(propYTitle=='undefined' || propYTitle==null),
62 text:propYTitle,
63 color:state.useLight?'black':'white',
64 font:{
65 size:20,
66 family:'mono',
67 weight:'normal'
68 },
69 },
70 ticks:{
71 color:state.useLight?'black':'white',
72 },
73 grid:{
74 display:ygrid,
75 color:state.useLight?'#bbb':'#444',
76 },
77 },
78 x:{
79 display:xaxis,
80 title:{
81 display:!(propXTitle=='undefined' || propXTitle==null),
82 text:propXTitle,
83 color:state.useLight?'black':'white',
84 padding:15,
85 font:{
86 size:20,
87 family:'mono',
88 weight:'normal'
89 },
90 },
91 ticks:{
92 color:state.useLight?'black':'white',
93 },
94 grid:{
95 display:xgrid,
96 color:state.useLight?'#bbb':'#444',
97 },
98 },
99 }
100 }
101return optionsObj
102};
Properly typing data, differentiating between context and state, optional and default parameters ect. The result is something pretty nice, but honestly limited. I've had difficulty writing animations and truely customizing the legend.
This open-source library may not be the right fit for most consumer-facing applications; I wouldn't use it for production in a financial service application. The main reason I chose it was for the number of GitHub stars it has, around three times more than any other JS charting library. This usually equates to better support and documentation.
The last thing I wanted to mention was Tailwind CSS. It is a godsend of a library. It does a great job of abstracting the most relevant CSS functionality so you can focus on the look, and not spend too much of your day knee-deep in moving rectangles around a screen.
There was so much I learned as a result of this project, too much to put in a simple blog post. Feel free to poke around the site or look at the source code.