forked from cadey/xesite
Add salary transparency page (#492)
* Move dhall data and types into `/dhall` folder * Reformat salary transparency data into Dhall * Wire up the old salary transparency page with a custom element * Wire up a new salary transparency page * Expose raw data as JSON * Make dhall types more portable * Remove gallery from the navbar * Make signal boost page point to the new data location * Add salary transparency page to the footer of the site * Add site update post for this Signed-off-by: Xe <me@xeiaso.net>
This commit is contained in:
parent
7541df7781
commit
ad6fba4c79
|
@ -3,6 +3,12 @@ title: My Career So Far in Dates/Titles/Salaries
|
||||||
date: 2019-03-14
|
date: 2019-03-14
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<div class="warning"><xeblog-conv name="Cadey" mood="coffee">This post is
|
||||||
|
outdated, see <a href="/salary-transparency">here</a> for more context on why
|
||||||
|
this data is made public. The table on this page will be automatically updated
|
||||||
|
to contain the data on my salary transparency page, but you should prefer that
|
||||||
|
page over this one when possible.</xeblog-conv></div>
|
||||||
|
|
||||||
Let this be inspiration to whoever is afraid of trying, failing and being fired.
|
Let this be inspiration to whoever is afraid of trying, failing and being fired.
|
||||||
Every single one of these jobs has taught me lessons I've used daily in my
|
Every single one of these jobs has taught me lessons I've used daily in my
|
||||||
career.
|
career.
|
||||||
|
@ -26,21 +32,7 @@ might not want.
|
||||||
The following table is a history of my software career by title, date and salary
|
The following table is a history of my software career by title, date and salary
|
||||||
(company names are omitted).
|
(company names are omitted).
|
||||||
|
|
||||||
| Title | Start Date | End Date | Days Worked | Days Between Jobs | Salary | How I Left |
|
<xeblog-salary-history></xeblog-salary-history>
|
||||||
|:----- |:---------- |:-------- |:----------- |:----------------- |:------ |:---------- |
|
|
||||||
| Junior Systems Administrator | November 11, 2013 | January 06, 2014 | 56 days | n/a | $50,000/year | Terminated |
|
|
||||||
| Software Engineering Intern | July 14, 2014 | August 27, 2014 | 44 days | 189 days | $35,000/year | Terminated |
|
|
||||||
| Consultant | September 17, 2014 | October 15, 2014 | 28 days | 21 days | $90/hour | Contract Lapsed |
|
|
||||||
| Consultant | October 27, 2014 | Feburary 9, 2015 | 105 days | 12 days | $90/hour | Contract Lapsed |
|
|
||||||
| Site Reliability Engineer | March 30, 2015 | March 7, 2016 | 343 days | 49 days | $125,000/year | Demoted |
|
|
||||||
| Systems Administrator | March 8, 2016 | April 1, 2016 | 24 days | 1 day | $105,000/year | Bad terms |
|
|
||||||
| Member of Technical Staff | April 4, 2016 | August 3, 2016 | 121 days | 3 days | $135,000/year | Bad terms |
|
|
||||||
| Software Engineer | August 24, 2016 | November 22, 2016 | 90 days | 21 days | $105,000/year | Terminated |
|
|
||||||
| Consultant | Feburary 13, 2017 | November 13, 2017 | 273 days | 83 days | don't remember | Hired |
|
|
||||||
| Senior Software Engineer | November 13, 2017 | March 8, 2019 | 480 days | 0 days | $150,000/year | Voulntary quit |
|
|
||||||
| Senior Site Reliability Expert | May 6, 2019 | October 27, 2020 | 540 days | 48 days | CAD$115,000/year (about USD$ 80k and change) | Voluntary quit |
|
|
||||||
| Software Designer | December 14, 2020 | *current* | n/a | n/a | CAD$135,000/year (about USD$ 105k and change) | raise |
|
|
||||||
| Archmage of Infrastructure | March 1, 2022 | *current* | n/a | n/a | CAD$147,150/year (about USD$ 115k and change) | n/a |
|
|
||||||
|
|
||||||
Even though I've been fired three times, I don't regret my career as it's been
|
Even though I've been fired three times, I don't regret my career as it's been
|
||||||
thus far. I've been able to work on experimental technology integrating into
|
thus far. I've been able to work on experimental technology integrating into
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
title: "Site Update: Salary Transparency Page Added"
|
||||||
|
date: 2022-06-14
|
||||||
|
author: Sephie
|
||||||
|
---
|
||||||
|
|
||||||
|
<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero>
|
||||||
|
|
||||||
|
I have added a [salary transparency
|
||||||
|
page](https://xeiaso.net/salary-transparency) to the blog. This page lists my
|
||||||
|
salary for every job I've had in tech. I have had this data open to the public
|
||||||
|
for years, but I feel this should be more prominently displayed on my website.
|
||||||
|
|
||||||
|
As someone who has seen pay discrimination work in action first-hand, data is
|
||||||
|
one of the ways that we can end this pointless hiding of information that leads
|
||||||
|
to people being uninformed and hirt by their lack of knowledge. By laying my
|
||||||
|
hand out in the open like this, I hope to ensure that people are better informed
|
||||||
|
about how much money they can make, so that they can be paid equally for equal
|
||||||
|
work.
|
||||||
|
|
||||||
|
Raw, machine processable data (including employer names) is available at
|
||||||
|
`/api/salary_transparency.json`. The JSON format is not stable. Do not treat it as
|
||||||
|
such. I reserve the right to change the formatting or semantics of the JSON
|
||||||
|
format at any time without warning. The raw data is in `/dhall/jobHistory.dhall`
|
||||||
|
in my site's git repository.
|
||||||
|
|
||||||
|
I have also taken the time to make sure that the [old
|
||||||
|
post](https://xeiaso.net/blog/my-career-in-dates-titles-salaries-2019-03-14)
|
||||||
|
maintains an up-to-date list. I do not want to break semantics on my website
|
||||||
|
without a very good reason. By leaving the old post un-updated, I feel it would
|
||||||
|
be doing a disservice to the community.
|
||||||
|
|
||||||
|
Please consider publishing your salary data like this as well. By open,
|
||||||
|
voulntary transparency we can help to end stigmas around discussing pay and help
|
||||||
|
ensure that the next generations of people in tech are treated fairly. Stigmas
|
||||||
|
thrive in darkness but die in the light of day. You can help end the stigma by
|
||||||
|
playing your cards out in the open like this.
|
||||||
|
|
||||||
|
It can be scary to do this; however every person that does it will make it that
|
||||||
|
much more easy for the next person to do it.
|
||||||
|
|
||||||
|
Don't be afraid.
|
74
config.dhall
74
config.dhall
|
@ -1,38 +1,8 @@
|
||||||
let Person =
|
let Person = ./dhall/types/Person.dhall
|
||||||
{ Type =
|
|
||||||
{ name : Text
|
|
||||||
, tags : List Text
|
|
||||||
, gitLink : Optional Text
|
|
||||||
, twitter : Optional Text
|
|
||||||
}
|
|
||||||
, default =
|
|
||||||
{ name = ""
|
|
||||||
, tags = [] : List Text
|
|
||||||
, gitLink = None Text
|
|
||||||
, twitter = None Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Author =
|
let Author = ./dhall/types/Author.dhall
|
||||||
{ Type =
|
|
||||||
{ name : Text
|
let Job = ./dhall/types/Job.dhall
|
||||||
, handle : Text
|
|
||||||
, picUrl : Optional Text
|
|
||||||
, link : Optional Text
|
|
||||||
, twitter : Optional Text
|
|
||||||
, default : Bool
|
|
||||||
, inSystem : Bool
|
|
||||||
}
|
|
||||||
, default =
|
|
||||||
{ name = ""
|
|
||||||
, handle = ""
|
|
||||||
, picUrl = None Text
|
|
||||||
, link = None Text
|
|
||||||
, twitter = None Text
|
|
||||||
, default = False
|
|
||||||
, inSystem = False
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let defaultPort = env:PORT ? 3030
|
let defaultPort = env:PORT ? 3030
|
||||||
|
|
||||||
|
@ -49,48 +19,24 @@ let Config =
|
||||||
, resumeFname : Text
|
, resumeFname : Text
|
||||||
, webMentionEndpoint : Text
|
, webMentionEndpoint : Text
|
||||||
, miToken : Text
|
, miToken : Text
|
||||||
|
, jobHistory : List Job.Type
|
||||||
}
|
}
|
||||||
, default =
|
, default =
|
||||||
{ signalboost = [] : List Person.Type
|
{ signalboost = [] : List Person.Type
|
||||||
, authors =
|
, authors = [] : List Author.Type
|
||||||
[ Author::{
|
|
||||||
, name = "Xe Iaso"
|
|
||||||
, handle = "xe"
|
|
||||||
, picUrl = Some "/static/img/avatar.png"
|
|
||||||
, link = Some "https://christine.website"
|
|
||||||
, twitter = Some "theprincessxena"
|
|
||||||
, default = True
|
|
||||||
, inSystem = True
|
|
||||||
}
|
|
||||||
, Author::{
|
|
||||||
, name = "Jessie"
|
|
||||||
, handle = "Heartmender"
|
|
||||||
, picUrl = Some
|
|
||||||
"https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg"
|
|
||||||
, link = Some "https://heartmender.writeas.com"
|
|
||||||
, twitter = Some "BeJustFine"
|
|
||||||
, inSystem = True
|
|
||||||
}
|
|
||||||
, Author::{
|
|
||||||
, name = "Ashe"
|
|
||||||
, handle = "ectamorphic"
|
|
||||||
, picUrl = Some
|
|
||||||
"https://cdn.christine.website/file/christine-static/img/FFVV1InX0AkDX3f_cropped_smol.jpg"
|
|
||||||
, inSystem = True
|
|
||||||
}
|
|
||||||
, Author::{ name = "Nicole", handle = "Twi", inSystem = True }
|
|
||||||
, Author::{ name = "Mai", handle = "Mai", inSystem = True }
|
|
||||||
]
|
|
||||||
, port = defaultPort
|
, port = defaultPort
|
||||||
, clackSet = [ "Ashlynn" ]
|
, clackSet = [ "Ashlynn" ]
|
||||||
, resumeFname = "./static/resume/resume.md"
|
, resumeFname = "./static/resume/resume.md"
|
||||||
, webMentionEndpoint = defaultWebMentionEndpoint
|
, webMentionEndpoint = defaultWebMentionEndpoint
|
||||||
, miToken = "${env:MI_TOKEN as Text ? ""}"
|
, miToken = "${env:MI_TOKEN as Text ? ""}"
|
||||||
|
, jobHistory = [] : List Job.Type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
in Config::{
|
in Config::{
|
||||||
, signalboost = ./signalboost.dhall
|
, signalboost = ./dhall/signalboost.dhall
|
||||||
|
, authors = ./dhall/authors.dhall
|
||||||
, clackSet =
|
, clackSet =
|
||||||
[ "Ashlynn", "Terry Davis", "Dennis Ritchie", "Steven Hawking" ]
|
[ "Ashlynn", "Terry Davis", "Dennis Ritchie", "Steven Hawking" ]
|
||||||
|
, jobHistory = ./dhall/jobHistory.dhall
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,6 @@ in pkgs.stdenv.mkDerivation {
|
||||||
cp -rf $src/blog $out/blog
|
cp -rf $src/blog $out/blog
|
||||||
cp -rf $src/css $out/css
|
cp -rf $src/css $out/css
|
||||||
cp -rf $src/gallery $out/gallery
|
cp -rf $src/gallery $out/gallery
|
||||||
cp -rf $src/signalboost.dhall $out/signalboost.dhall
|
|
||||||
cp -rf $src/static $out/static
|
cp -rf $src/static $out/static
|
||||||
cp -rf $src/talks $out/talks
|
cp -rf $src/talks $out/talks
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
let Author = ./types/Author.dhall
|
||||||
|
|
||||||
|
in [ Author::{
|
||||||
|
, name = "Xe Iaso"
|
||||||
|
, handle = "xe"
|
||||||
|
, picUrl = Some "/static/img/avatar.png"
|
||||||
|
, link = Some "https://christine.website"
|
||||||
|
, twitter = Some "theprincessxena"
|
||||||
|
, default = True
|
||||||
|
, inSystem = True
|
||||||
|
}
|
||||||
|
, Author::{
|
||||||
|
, name = "Jessie"
|
||||||
|
, handle = "Heartmender"
|
||||||
|
, picUrl = Some
|
||||||
|
"https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg"
|
||||||
|
, link = Some "https://heartmender.writeas.com"
|
||||||
|
, twitter = Some "BeJustFine"
|
||||||
|
, inSystem = True
|
||||||
|
}
|
||||||
|
, Author::{
|
||||||
|
, name = "Ashe"
|
||||||
|
, handle = "ectamorphic"
|
||||||
|
, picUrl = Some
|
||||||
|
"https://cdn.christine.website/file/christine-static/img/FFVV1InX0AkDX3f_cropped_smol.jpg"
|
||||||
|
, inSystem = True
|
||||||
|
}
|
||||||
|
, Author::{ name = "Nicole", handle = "Twi", inSystem = True }
|
||||||
|
, Author::{ name = "Mai", handle = "Mai", inSystem = True }
|
||||||
|
, Author::{ name = "Sephira", handle = "Sephie", inSystem = True }
|
||||||
|
]
|
|
@ -0,0 +1,136 @@
|
||||||
|
let Job = ./types/Job.dhall
|
||||||
|
|
||||||
|
let Salary = ./types/Salary.dhall
|
||||||
|
|
||||||
|
let annual = \(rate : Natural) -> Salary::{ amount = rate }
|
||||||
|
|
||||||
|
let hourly = \(rate : Natural) -> Salary::{ amount = rate, per = "hour" }
|
||||||
|
|
||||||
|
let annualCAD = \(rate : Natural) -> Salary::{ amount = rate, currency = "CAD" }
|
||||||
|
|
||||||
|
in [ Job::{
|
||||||
|
, company = "Symplicity"
|
||||||
|
, title = "Junior Systems Administrator"
|
||||||
|
, startDate = "2013-11-11"
|
||||||
|
, endDate = Some "2014-01-06"
|
||||||
|
, daysWorked = Some 56
|
||||||
|
, salary = annual 50000
|
||||||
|
, leaveReason = Some "terminated"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "OpDemand"
|
||||||
|
, title = "Software Engineering Intern"
|
||||||
|
, startDate = "2014-07-14"
|
||||||
|
, endDate = Some "2014-08-27"
|
||||||
|
, daysWorked = Some 44
|
||||||
|
, daysBetween = Some 189
|
||||||
|
, salary = annual 35000
|
||||||
|
, leaveReason = Some "terminated"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Crowdflower (contract)"
|
||||||
|
, title = "Consultant"
|
||||||
|
, startDate = "2014-09-17"
|
||||||
|
, endDate = Some "2014-10-15"
|
||||||
|
, daysWorked = Some 28
|
||||||
|
, daysBetween = Some 21
|
||||||
|
, salary = hourly 90
|
||||||
|
, leaveReason = Some "contract not renewed"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "VTCSecure (contract)"
|
||||||
|
, title = "Consultant"
|
||||||
|
, startDate = "2014-10-27"
|
||||||
|
, endDate = Some "2015-02-09"
|
||||||
|
, daysWorked = Some 105
|
||||||
|
, daysBetween = Some 12
|
||||||
|
, salary = hourly 90
|
||||||
|
, leaveReason = Some "contract not renewed"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "IMVU"
|
||||||
|
, title = "Site Reliability Engineer"
|
||||||
|
, startDate = "2015-03-30"
|
||||||
|
, endDate = Some "2016-03-07"
|
||||||
|
, daysWorked = Some 343
|
||||||
|
, daysBetween = Some 49
|
||||||
|
, salary = annual 125000
|
||||||
|
, leaveReason = Some "demoted"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "IMVU"
|
||||||
|
, title = "Systems Administrator"
|
||||||
|
, startDate = "2016-03-08"
|
||||||
|
, endDate = Some "2016-04-01"
|
||||||
|
, daysWorked = Some 24
|
||||||
|
, daysBetween = Some 1
|
||||||
|
, salary = annual 105000
|
||||||
|
, leaveReason = Some "quit"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Pure Storage"
|
||||||
|
, title = "Member of Technical Staff"
|
||||||
|
, startDate = "2016-04-04"
|
||||||
|
, endDate = Some "2016-08-03"
|
||||||
|
, daysWorked = Some 121
|
||||||
|
, daysBetween = Some 3
|
||||||
|
, salary = annual 135000
|
||||||
|
, leaveReason = Some "quit"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Backplane.io (defunct)"
|
||||||
|
, title = "Software Engineer"
|
||||||
|
, startDate = "2016-08-24"
|
||||||
|
, endDate = Some "2016-11-22"
|
||||||
|
, daysWorked = Some 90
|
||||||
|
, daysBetween = Some 21
|
||||||
|
, salary = annual 105000
|
||||||
|
, leaveReason = Some "terminated"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Heroku (contract)"
|
||||||
|
, title = "Consultant"
|
||||||
|
, startDate = "2017-02-13"
|
||||||
|
, endDate = Some "2017-11-13"
|
||||||
|
, daysWorked = Some 273
|
||||||
|
, daysBetween = Some 83
|
||||||
|
, salary = hourly 120
|
||||||
|
, leaveReason = Some "hired"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Heroku"
|
||||||
|
, title = "Senior Software Engineer"
|
||||||
|
, startDate = "2017-11-13"
|
||||||
|
, endDate = Some "2019-03-08"
|
||||||
|
, daysWorked = Some 480
|
||||||
|
, daysBetween = Some 0
|
||||||
|
, salary = annual 150000
|
||||||
|
, leaveReason = Some "quit"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Lightspeed POS"
|
||||||
|
, title = "Expert principal en fiabilité du site"
|
||||||
|
, startDate = "2019-05-06"
|
||||||
|
, endDate = Some "2020-11-27"
|
||||||
|
, daysWorked = Some 540
|
||||||
|
, daysBetween = Some 48
|
||||||
|
, salary = annualCAD 115000
|
||||||
|
, leaveReason = Some "quit"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Tailscale"
|
||||||
|
, title = "Software Designer"
|
||||||
|
, startDate = "2020-12-14"
|
||||||
|
, endDate = Some "2022-03-01"
|
||||||
|
, daysWorked = Some 442
|
||||||
|
, daysBetween = Some 0
|
||||||
|
, salary = annualCAD 135000
|
||||||
|
, leaveReason = Some "raise"
|
||||||
|
}
|
||||||
|
, Job::{
|
||||||
|
, company = "Tailscale"
|
||||||
|
, title = "Archmage of Infrastructure"
|
||||||
|
, startDate = "2022-03-01"
|
||||||
|
, salary = annualCAD 147150
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,17 +1,4 @@
|
||||||
let Person =
|
let Person = ./types/Person.dhall
|
||||||
{ Type =
|
|
||||||
{ name : Text
|
|
||||||
, tags : List Text
|
|
||||||
, gitLink : Optional Text
|
|
||||||
, twitter : Optional Text
|
|
||||||
}
|
|
||||||
, default =
|
|
||||||
{ name = ""
|
|
||||||
, tags = [] : List Text
|
|
||||||
, gitLink = None Text
|
|
||||||
, twitter = None Text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
in [ Person::{
|
in [ Person::{
|
||||||
, name = "Christian Sullivan"
|
, name = "Christian Sullivan"
|
||||||
|
@ -278,20 +265,20 @@ in [ Person::{
|
||||||
, gitLink = Some "https://github.com/henri"
|
, gitLink = Some "https://github.com/henri"
|
||||||
, twitter = Some "https://twitter.com/henri_shustak"
|
, twitter = Some "https://twitter.com/henri_shustak"
|
||||||
}
|
}
|
||||||
, Person::{
|
, Person::{
|
||||||
, name = "Gabriel Simmer"
|
, name = "Gabriel Simmer"
|
||||||
, tags =
|
, tags =
|
||||||
[ "golang"
|
[ "golang"
|
||||||
, "backend"
|
, "backend"
|
||||||
, "javascript"
|
, "javascript"
|
||||||
, "python"
|
, "python"
|
||||||
, "software"
|
, "software"
|
||||||
, "full-stack"
|
, "full-stack"
|
||||||
, "linux"
|
, "linux"
|
||||||
, "devops"
|
, "devops"
|
||||||
, "developer tooling"
|
, "developer tooling"
|
||||||
]
|
]
|
||||||
, gitLink = Some "https://github.com/gmemstr"
|
, gitLink = Some "https://github.com/gmemstr"
|
||||||
, twitter = Some "https://twitter.com/gmem_"
|
, twitter = Some "https://twitter.com/gmem_"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
{ Type =
|
||||||
|
{ name : Text
|
||||||
|
, handle : Text
|
||||||
|
, picUrl : Optional Text
|
||||||
|
, link : Optional Text
|
||||||
|
, twitter : Optional Text
|
||||||
|
, default : Bool
|
||||||
|
, inSystem : Bool
|
||||||
|
}
|
||||||
|
, default =
|
||||||
|
{ name = ""
|
||||||
|
, handle = ""
|
||||||
|
, picUrl = None Text
|
||||||
|
, link = None Text
|
||||||
|
, twitter = None Text
|
||||||
|
, default = False
|
||||||
|
, inSystem = False
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
let Salary = ./Salary.dhall
|
||||||
|
|
||||||
|
in { Type =
|
||||||
|
{ company : Text
|
||||||
|
, title : Text
|
||||||
|
, startDate : Text
|
||||||
|
, endDate : Optional Text
|
||||||
|
, daysWorked : Optional Natural
|
||||||
|
, daysBetween : Optional Natural
|
||||||
|
, salary : Salary.Type
|
||||||
|
, leaveReason : Optional Text
|
||||||
|
}
|
||||||
|
, default =
|
||||||
|
{ company = "Unknown"
|
||||||
|
, title = "Unknown"
|
||||||
|
, startDate = "0000-01-01"
|
||||||
|
, endDate = None Text
|
||||||
|
, daysWorked = None Natural
|
||||||
|
, daysBetween = None Natural
|
||||||
|
, salary = Salary::{=}
|
||||||
|
, leaveReason = None Text
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{ Type =
|
||||||
|
{ name : Text
|
||||||
|
, tags : List Text
|
||||||
|
, gitLink : Optional Text
|
||||||
|
, twitter : Optional Text
|
||||||
|
}
|
||||||
|
, default =
|
||||||
|
{ name = "", tags = [] : List Text, gitLink = None Text, twitter = None Text }
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{ Type = { amount : Natural, currency : Text, per : Text }
|
||||||
|
, default = { amount = 0, currency = "USD", per = "year" }
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::app::Config;
|
||||||
use crate::templates::Html;
|
use crate::templates::Html;
|
||||||
use color_eyre::eyre::{Result, WrapErr};
|
use color_eyre::eyre::{Result, WrapErr};
|
||||||
use comrak::nodes::{Ast, AstNode, NodeValue};
|
use comrak::nodes::{Ast, AstNode, NodeValue};
|
||||||
|
@ -9,13 +10,14 @@ use comrak::{
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use lol_html::{element, html_content::ContentType, rewrite_str, RewriteStrSettings};
|
use lol_html::{element, html_content::ContentType, rewrite_str, RewriteStrSettings};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::sync::Arc;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SYNTECT_ADAPTER: SyntectAdapter<'static> = SyntectAdapter::new("base16-mocha.dark");
|
static ref SYNTECT_ADAPTER: SyntectAdapter<'static> = SyntectAdapter::new("base16-mocha.dark");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(inp: &str) -> Result<String> {
|
pub fn render(cfg: Arc<Config>, inp: &str) -> Result<String> {
|
||||||
let mut options = ComrakOptions::default();
|
let mut options = ComrakOptions::default();
|
||||||
|
|
||||||
options.extension.autolink = true;
|
options.extension.autolink = true;
|
||||||
|
@ -99,6 +101,11 @@ pub fn render(inp: &str) -> Result<String> {
|
||||||
element!("xeblog-hero", |el| {
|
element!("xeblog-hero", |el| {
|
||||||
let file = el.get_attribute("file").expect("wanted xeblog-hero to contain file");
|
let file = el.get_attribute("file").expect("wanted xeblog-hero to contain file");
|
||||||
el.replace(&crate::tmpl::xeblog_hero(file, el.get_attribute("prompt")).0, ContentType::Html);
|
el.replace(&crate::tmpl::xeblog_hero(file, el.get_attribute("prompt")).0, ContentType::Html);
|
||||||
|
Ok(())
|
||||||
|
}),
|
||||||
|
element!("xeblog-salary-history", |el| {
|
||||||
|
el.replace(&crate::tmpl::xeblog_salary_history(cfg.clone()).0, ContentType::Html);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,23 +1,73 @@
|
||||||
use crate::{post::Post, signalboost::Person};
|
use crate::{post::Post, signalboost::Person};
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use serde::Deserialize;
|
use color_eyre::eyre::Result;
|
||||||
|
use maud::{html, Markup};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
|
fmt::{self, Display},
|
||||||
fs,
|
fs,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tracing::{error, instrument};
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
pub mod markdown;
|
pub mod markdown;
|
||||||
pub mod poke;
|
pub mod poke;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub(crate) signalboost: Vec<Person>,
|
pub(crate) signalboost: Vec<Person>,
|
||||||
#[serde(rename = "resumeFname")]
|
#[serde(rename = "resumeFname")]
|
||||||
pub(crate) resume_fname: PathBuf,
|
pub(crate) resume_fname: PathBuf,
|
||||||
#[serde(rename = "miToken")]
|
#[serde(rename = "miToken")]
|
||||||
pub(crate) mi_token: String,
|
pub(crate) mi_token: String,
|
||||||
|
#[serde(rename = "jobHistory")]
|
||||||
|
pub(crate) job_history: Vec<Job>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Default)]
|
||||||
|
pub struct Salary {
|
||||||
|
pub amount: i32,
|
||||||
|
pub per: String,
|
||||||
|
pub currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Salary {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}${}/{}", self.currency, self.amount, self.per)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Serialize, Default)]
|
||||||
|
pub struct Job {
|
||||||
|
pub company: String,
|
||||||
|
pub title: String,
|
||||||
|
#[serde(rename = "startDate")]
|
||||||
|
pub start_date: String,
|
||||||
|
#[serde(rename = "endDate")]
|
||||||
|
pub end_date: Option<String>,
|
||||||
|
#[serde(rename = "daysWorked")]
|
||||||
|
pub days_worked: Option<i32>,
|
||||||
|
#[serde(rename = "daysBetween")]
|
||||||
|
pub days_between: Option<i32>,
|
||||||
|
pub salary: Salary,
|
||||||
|
#[serde(rename = "leaveReason")]
|
||||||
|
pub leave_reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Job {
|
||||||
|
pub fn pay_history_row(&self) -> Markup {
|
||||||
|
html! {
|
||||||
|
tr {
|
||||||
|
td { (self.title) }
|
||||||
|
td { (self.start_date) }
|
||||||
|
td { (self.end_date.as_ref().unwrap_or(&"current".to_string())) }
|
||||||
|
td { (if self.days_worked.is_some() { self.days_worked.as_ref().unwrap().to_string() } else { "n/a".to_string() }) }
|
||||||
|
td { (self.salary) }
|
||||||
|
td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument]
|
#[instrument]
|
||||||
|
@ -57,7 +107,7 @@ async fn patrons() -> Result<Option<patreon::Users>> {
|
||||||
pub const ICON: &'static str = "https://xeiaso.net/static/img/avatar.png";
|
pub const ICON: &'static str = "https://xeiaso.net/static/img/avatar.png";
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub cfg: Config,
|
pub cfg: Arc<Config>,
|
||||||
pub signalboost: Vec<Person>,
|
pub signalboost: Vec<Person>,
|
||||||
pub resume: String,
|
pub resume: String,
|
||||||
pub blog: Vec<Post>,
|
pub blog: Vec<Post>,
|
||||||
|
@ -71,14 +121,17 @@ pub struct State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(cfg: PathBuf) -> Result<State> {
|
pub async fn init(cfg: PathBuf) -> Result<State> {
|
||||||
let cfg: Config = serde_dhall::from_file(cfg).parse()?;
|
let cfg: Arc<Config> = Arc::new(serde_dhall::from_file(cfg).parse()?);
|
||||||
let sb = cfg.signalboost.clone();
|
let sb = cfg.signalboost.clone();
|
||||||
let resume = fs::read_to_string(cfg.resume_fname.clone())?;
|
let resume = fs::read_to_string(cfg.clone().resume_fname.clone())?;
|
||||||
let resume: String = markdown::render(&resume)?;
|
let resume: String = markdown::render(cfg.clone(), &resume)?;
|
||||||
let mi = mi::Client::new(cfg.mi_token.clone(), crate::APPLICATION_NAME.to_string())?;
|
let mi = mi::Client::new(
|
||||||
let blog = crate::post::load("blog").await?;
|
cfg.clone().mi_token.clone(),
|
||||||
let gallery = crate::post::load("gallery").await?;
|
crate::APPLICATION_NAME.to_string(),
|
||||||
let talks = crate::post::load("talks").await?;
|
)?;
|
||||||
|
let blog = crate::post::load(cfg.clone(), "blog").await?;
|
||||||
|
let gallery = crate::post::load(cfg.clone(), "gallery").await?;
|
||||||
|
let talks = crate::post::load(cfg.clone(), "talks").await?;
|
||||||
let mut everything: Vec<Post> = vec![];
|
let mut everything: Vec<Post> = vec![];
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
use crate::{app::State, templates};
|
use crate::{
|
||||||
|
app::{Job, State},
|
||||||
|
templates,
|
||||||
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
body,
|
body,
|
||||||
extract::Extension,
|
extract::Extension,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
|
Json,
|
||||||
};
|
};
|
||||||
use chrono::{Datelike, Timelike, Utc, Weekday};
|
use chrono::{Datelike, Timelike, Utc, Weekday};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
@ -72,6 +76,28 @@ pub async fn feeds() -> Result {
|
||||||
Ok(Html(result))
|
Ok(Html(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum_macros::debug_handler]
|
||||||
|
#[instrument(skip(state))]
|
||||||
|
pub async fn salary_transparency(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
|
HIT_COUNTER
|
||||||
|
.with_label_values(&["salary_transparency"])
|
||||||
|
.inc();
|
||||||
|
let state = state.clone();
|
||||||
|
let mut result: Vec<u8> = vec![];
|
||||||
|
templates::salary_transparency(&mut result, state.cfg.clone())?;
|
||||||
|
Ok(Html(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum_macros::debug_handler]
|
||||||
|
#[instrument(skip(state))]
|
||||||
|
pub async fn salary_transparency_json(Extension(state): Extension<Arc<State>>) -> Json<Vec<Job>> {
|
||||||
|
HIT_COUNTER
|
||||||
|
.with_label_values(&["salary_transparency_json"])
|
||||||
|
.inc();
|
||||||
|
|
||||||
|
Json(state.clone().cfg.clone().job_history.clone())
|
||||||
|
}
|
||||||
|
|
||||||
#[axum_macros::debug_handler]
|
#[axum_macros::debug_handler]
|
||||||
#[instrument(skip(state))]
|
#[instrument(skip(state))]
|
||||||
pub async fn resume(Extension(state): Extension<Arc<State>>) -> Result {
|
pub async fn resume(Extension(state): Extension<Arc<State>>) -> Result {
|
||||||
|
|
|
@ -155,6 +155,11 @@ async fn main() -> Result<()> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
// api
|
||||||
|
.route(
|
||||||
|
"/api/salary_transparency.json",
|
||||||
|
get(handlers::salary_transparency_json),
|
||||||
|
)
|
||||||
// static pages
|
// static pages
|
||||||
.route("/", get(handlers::index))
|
.route("/", get(handlers::index))
|
||||||
.route("/contact", get(handlers::contact))
|
.route("/contact", get(handlers::contact))
|
||||||
|
@ -162,6 +167,7 @@ async fn main() -> Result<()> {
|
||||||
.route("/resume", get(handlers::resume))
|
.route("/resume", get(handlers::resume))
|
||||||
.route("/patrons", get(handlers::patrons))
|
.route("/patrons", get(handlers::patrons))
|
||||||
.route("/signalboost", get(handlers::signalboost))
|
.route("/signalboost", get(handlers::signalboost))
|
||||||
|
.route("/salary-transparency", get(handlers::salary_transparency))
|
||||||
// feeds
|
// feeds
|
||||||
.route("/blog.json", get(handlers::feeds::jsonfeed))
|
.route("/blog.json", get(handlers::feeds::jsonfeed))
|
||||||
.route("/blog.atom", get(handlers::feeds::atom))
|
.route("/blog.atom", get(handlers::feeds::atom))
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use crate::app::Config;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use color_eyre::eyre::{eyre, Result, WrapErr};
|
use color_eyre::eyre::{eyre, Result, WrapErr};
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{borrow::Borrow, cmp::Ordering, path::PathBuf};
|
use std::{borrow::Borrow, cmp::Ordering, path::PathBuf, sync::Arc};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
|
||||||
pub mod frontmatter;
|
pub mod frontmatter;
|
||||||
|
@ -81,7 +82,12 @@ impl Post {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> {
|
async fn read_post(
|
||||||
|
cfg: Arc<Config>,
|
||||||
|
dir: &str,
|
||||||
|
fname: PathBuf,
|
||||||
|
cli: &Option<mi::Client>,
|
||||||
|
) -> Result<Post> {
|
||||||
debug!(
|
debug!(
|
||||||
"loading {}",
|
"loading {}",
|
||||||
fname.clone().into_os_string().into_string().unwrap()
|
fname.clone().into_os_string().into_string().unwrap()
|
||||||
|
@ -96,7 +102,7 @@ async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Resul
|
||||||
let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d")
|
let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d")
|
||||||
.map_err(|why| eyre!("error parsing date in {:?}: {}", fname, why))?;
|
.map_err(|why| eyre!("error parsing date in {:?}: {}", fname, why))?;
|
||||||
let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap());
|
let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap());
|
||||||
let body_html = crate::app::markdown::render(&body)
|
let body_html = crate::app::markdown::render(cfg.clone(), &body)
|
||||||
.wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?;
|
.wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?;
|
||||||
let date: DateTime<FixedOffset> =
|
let date: DateTime<FixedOffset> =
|
||||||
DateTime::<Utc>::from_utc(NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)), Utc)
|
DateTime::<Utc>::from_utc(NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)), Utc)
|
||||||
|
@ -144,7 +150,7 @@ async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Resul
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load(dir: &str) -> Result<Vec<Post>> {
|
pub async fn load(cfg: Arc<Config>, dir: &str) -> Result<Vec<Post>> {
|
||||||
let cli = match std::env::var("MI_TOKEN") {
|
let cli = match std::env::var("MI_TOKEN") {
|
||||||
Ok(token) => mi::Client::new(token.to_string(), crate::APPLICATION_NAME.to_string()).ok(),
|
Ok(token) => mi::Client::new(token.to_string(), crate::APPLICATION_NAME.to_string()).ok(),
|
||||||
Err(_) => None,
|
Err(_) => None,
|
||||||
|
@ -152,7 +158,7 @@ pub async fn load(dir: &str) -> Result<Vec<Post>> {
|
||||||
|
|
||||||
let futs = glob(&format!("{}/*.markdown", dir))?
|
let futs = glob(&format!("{}/*.markdown", dir))?
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.map(|fname| read_post(dir, fname, cli.borrow()));
|
.map(|fname| read_post(cfg.clone(), dir, fname, cli.borrow()));
|
||||||
|
|
||||||
let mut result: Vec<Post> = futures::future::join_all(futs)
|
let mut result: Vec<Post> = futures::future::join_all(futs)
|
||||||
.await
|
.await
|
||||||
|
@ -172,25 +178,30 @@ pub async fn load(dir: &str) -> Result<Vec<Post>> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::app::Config;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn blog() {
|
async fn blog() {
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
load("blog").await.expect("posts to load");
|
let cfg = Arc::new(Config::default());
|
||||||
|
load(cfg, "blog").await.expect("posts to load");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gallery() -> Result<()> {
|
async fn gallery() -> Result<()> {
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
load("gallery").await?;
|
let cfg = Arc::new(Config::default());
|
||||||
|
load(cfg, "gallery").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn talks() -> Result<()> {
|
async fn talks() -> Result<()> {
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
load("talks").await?;
|
let cfg = Arc::new(Config::default());
|
||||||
|
load(cfg, "talks").await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ mod tests {
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
#[test]
|
#[test]
|
||||||
fn load() -> Result<()> {
|
fn load() -> Result<()> {
|
||||||
let _people: Vec<super::Person> = serde_dhall::from_file("./signalboost.dhall").parse()?;
|
let _people: Vec<super::Person> =
|
||||||
|
serde_dhall::from_file("./dhall/signalboost.dhall").parse()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,27 @@
|
||||||
|
use crate::app::Config;
|
||||||
use maud::{html, Markup};
|
use maud::{html, Markup};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod nag;
|
pub mod nag;
|
||||||
|
|
||||||
|
pub fn xeblog_salary_history(cfg: Arc<Config>) -> Markup {
|
||||||
|
html! {
|
||||||
|
table.salary_history {
|
||||||
|
tr {
|
||||||
|
th { "Title" }
|
||||||
|
th { "Start Date" }
|
||||||
|
th { "End Date" }
|
||||||
|
th { "Days Worked" }
|
||||||
|
th { "Salary" }
|
||||||
|
th { "How I Left" }
|
||||||
|
}
|
||||||
|
@for job in &cfg.clone().job_history {
|
||||||
|
(job.pay_history_row())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn xeblog_hero(file: String, prompt: Option<String>) -> Markup {
|
pub fn xeblog_hero(file: String, prompt: Option<String>) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
figure.hero style="margin:0" {
|
figure.hero style="margin:0" {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
<p>Like what you see? Donate on <a href="https://www.patreon.com/cadey">Patreon</a> like <a href="/patrons">these awesome people</a>!</p>
|
<p>Like what you see? Donate on <a href="https://www.patreon.com/cadey">Patreon</a> like <a href="/patrons">these awesome people</a>!</p>
|
||||||
<p>Looking for someone for your team? Take a look <a href="/signalboost">here</a>.</p>
|
<p>Looking for someone for your team? Take a look <a href="/signalboost">here</a>.</p>
|
||||||
<p>Served by @env!("out")/bin/xesite</a>, see <a href="https://github.com/Xe/site">source code here</a>.</p>
|
<p>Served by @env!("out")/bin/xesite</a>, see <a href="https://github.com/Xe/site">source code here</a>.</p>
|
||||||
|
<p>See my <a href="/salary-transparency">salary transparency data here</a>.</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -93,7 +93,7 @@ la budza pu cusku lu
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<header>
|
||||||
<span class="logo"></span>
|
<span class="logo"></span>
|
||||||
<nav><a href="/">Xe</a> - <a href="/blog">Blog</a> - <a href="/contact">Contact</a> - <a href="/gallery">Gallery</a> - <a href="/resume">Resume</a> - <a href="/talks">Talks</a> - <a href="/signalboost">Signal Boost</a> - <a href="/feeds">Feeds</a> | <a target="_blank" rel="noopener noreferrer" href="https://graphviz.christine.website">GraphViz</a> - <a target="_blank" rel="noopener noreferrer" href="https://when-then-zen.christine.website/">When Then Zen</a></nav>
|
<nav><a href="/">Xe</a> - <a href="/blog">Blog</a> - <a href="/contact">Contact</a> - <a href="/resume">Resume</a> - <a href="/talks">Talks</a> - <a href="/signalboost">Signal Boost</a> - <a href="/feeds">Feeds</a> | <a target="_blank" rel="noopener noreferrer" href="https://graphviz.christine.website">GraphViz</a> - <a target="_blank" rel="noopener noreferrer" href="https://when-then-zen.christine.website/">When Then Zen</a></nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
@use super::{header_html, footer_html};
|
||||||
|
@use crate::{app::Config, tmpl::xeblog_salary_history};
|
||||||
|
@use std::sync::Arc;
|
||||||
|
|
||||||
|
@(cfg: Arc<Config>)
|
||||||
|
|
||||||
|
@:header_html(Some("Salary Transparency"), None)
|
||||||
|
|
||||||
|
<h1>Salary Transparency</h1>
|
||||||
|
|
||||||
|
<p>This page lists my salary for every job I've had in tech. I have had this data open to the public <a href="https://xeiaso.net/blog/my-career-in-dates-titles-salaries-2019-03-14">for years</a>, but I feel this should be more prominently displayed on my website. Other people have copied my approach of having a list of every salary they have ever been payed on their websites, and I would like to set the example by making it prominent on my website.</p>
|
||||||
|
|
||||||
|
<p>As someone who has seen pay discrimination work in action first-hand, data is one of the ways that we can end this pointless hiding of information that leads to people being uninformed and hirt by their lack of knowledge. By laying my hand out in the open like this, I hope to ensure that people are better informed about how much money they <i>can</i> make, so that they can be paid equally for equal work.</p>
|
||||||
|
|
||||||
|
<h2>Salary Data</h2>
|
||||||
|
|
||||||
|
<p>To get this data, I have scoured over past emails, contracts and everything so that I can be sure that this information is as accurate as possible. The data on this page intentionally omits employer names.</p>
|
||||||
|
|
||||||
|
<p>Raw data (including employer names) is available at <code>/api/salary_transparency.json</code>. The JSON format is not stable. Do not treat it as such. I reserve the right to change the formatting or semantics of the JSON format at any time without warning. The raw data is in <code>/dhall/jobHistory.dhall</code> in my site's git repository.</p>
|
||||||
|
|
||||||
|
@Html(xeblog_salary_history(cfg.clone()).0)
|
||||||
|
|
||||||
|
<p>I typically update this page once any of the following things happens:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>I quit a job.</li>
|
||||||
|
<li>I get a raise/title change at the same company.</li>
|
||||||
|
<li>I get terminated from a job.</li>
|
||||||
|
<li>I get converted from a contracter to a full-time employee.</li>
|
||||||
|
<li>Other unspecified extranormal events happen.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Please consider publishing your salary data like this as well. By open, voulntary transparency we can help to end stigmas around discussing pay and help ensure that the next generations of people in tech are treated fairly. Stigmas thrive in darkness but die in the light of day. You can help end the stigma by playing your cards out in the open like this.</p>
|
||||||
|
|
||||||
|
@:footer_html()
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
<p>These awesome people are currently looking for a job. If you are looking for anyone with these skills, please feel free to reach out to them.</p>
|
<p>These awesome people are currently looking for a job. If you are looking for anyone with these skills, please feel free to reach out to them.</p>
|
||||||
|
|
||||||
<p>To add yourself to this list, fork <a href="https://github.com/Xe/site">this website's source code</a> and send a pull request with edits to <code>signalboost.dhall</code>.</p>
|
<p>To add yourself to this list, fork <a href="https://github.com/Xe/site">this website's source code</a> and send a pull request with edits to <code>/dhall/signalboost.dhall</code>.</p>
|
||||||
|
|
||||||
<!-- TODO(Xe): Remove this after COVID-19 is less of a thing -->
|
<!-- TODO(Xe): Remove this after COVID-19 is less of a thing -->
|
||||||
<p>With COVID-19 raging across the world, these people are in need of a job now more than ever.</p>
|
<p>With COVID-19 raging across the world, these people are in need of a job now more than ever.</p>
|
||||||
|
|
Loading…
Reference in New Issue