├── .github
├── dependabot.yml
└── workflows
│ ├── golangci-lint.yml
│ ├── publish-website.yml
│ └── update-stats.yml
├── .gitignore
├── README.md
├── analysis-latest.csv
├── commits-history-30d.json
├── dep-repo-latest.csv
├── go.mod
├── go.sum
├── last-update.txt
├── main.go
├── otel_instrumentation
└── opentelemetry_setup.go
├── stars-history-30d.json
├── update.sh
└── website
├── .eslintrc.cjs
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
├── favicon.png
└── vite.svg
├── src
├── App.css
├── App.tsx
├── BubbleChart.tsx
├── DepsChart.tsx
├── Header.jsx
├── TimeSeriesChart.jsx
├── WaffleChart.tsx
├── assets
│ └── react.svg
├── index.css
├── main.tsx
├── schema_commits.js
├── schema_stars.js
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gomod
4 | directory: /
5 | schedule:
6 | interval: weekly
7 |
8 | - package-ecosystem: "github-actions"
9 | directory: "/"
10 | schedule:
11 | interval: weekly
12 |
13 | - package-ecosystem: "npm"
14 | directory: "/website"
15 | schedule:
16 | interval: weekly
--------------------------------------------------------------------------------
/.github/workflows/golangci-lint.yml:
--------------------------------------------------------------------------------
1 | name: Golangci Lint Check
2 |
3 | on:
4 | push:
5 | branches:
6 | - "main"
7 | paths-ignore:
8 | - "**.md"
9 | - LICENSE
10 | - ".github/ISSUE_TEMPLATE/*.yml"
11 | - ".github/dependabot.yml"
12 | - "website/**"
13 | - "*.csv"
14 | - "*.txt"
15 | pull_request:
16 | branches:
17 | - "*"
18 | paths-ignore:
19 | - "**.md"
20 | - LICENSE
21 | - ".github/ISSUE_TEMPLATE/*.yml"
22 | - ".github/dependabot.yml"
23 | - "website/**"
24 | - "*.csv"
25 | - "*.txt"
26 |
27 | jobs:
28 | golangci-lint:
29 | runs-on: ubuntu-latest
30 | steps:
31 | - name: Fetch Repository
32 | uses: actions/checkout@v4
33 | - name: Run golangci-lint
34 | uses: reviewdog/action-golangci-lint@v2
35 | with:
36 | golangci_lint_flags: "--tests=false"
37 |
--------------------------------------------------------------------------------
/.github/workflows/publish-website.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy static content to Pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 | paths:
9 | - "website/**"
10 | - "*.csv"
11 | - "*.txt"
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages
17 | permissions:
18 | contents: read
19 | pages: write
20 | id-token: write
21 |
22 | # Allow one concurrent deployment
23 | concurrency:
24 | group: "pages"
25 | cancel-in-progress: true
26 |
27 | jobs:
28 | # Single deploy job since we're just deploying
29 | deploy:
30 | environment:
31 | name: github-pages
32 | url: ${{ steps.deployment.outputs.page_url }}
33 | runs-on: ubuntu-latest
34 | defaults:
35 | run:
36 | working-directory: ./website
37 | steps:
38 | - name: Checkout
39 | uses: actions/checkout@v4
40 |
41 | - name: Set up Node
42 | uses: actions/setup-node@v4
43 | with:
44 | node-version: 20
45 |
46 | - name: Install dependencies
47 | run: npm install
48 |
49 | - name: Build
50 | run: npm run build
51 |
52 | - name: Setup Pages
53 | uses: actions/configure-pages@v5
54 |
55 | - run: ls
56 |
57 | - name: Upload artifact
58 | uses: actions/upload-pages-artifact@v3
59 | with:
60 | path: "./website/dist"
61 |
62 | - name: Deploy to GitHub Pages
63 | id: deployment
64 | uses: actions/deploy-pages@v4
65 |
--------------------------------------------------------------------------------
/.github/workflows/update-stats.yml:
--------------------------------------------------------------------------------
1 | name: Update Awesome Go stats
2 |
3 | on:
4 | schedule:
5 | - cron: "0 3 * * *" # runs at 03:00 UTC everyday
6 | workflow_dispatch:
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - name: Set up Go
18 | uses: actions/setup-go@v5
19 | with:
20 | go-version: "1.23.3"
21 |
22 | - name: Install dependencies
23 | run: go get .
24 |
25 | - name: Run
26 | run: go run main.go
27 | env:
28 | PAT: ${{ secrets.PAT }}
29 | OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}
30 | OTEL_SERVICE_NAME: ${{ vars.OTEL_SERVICE_NAME }}
31 | OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OTEL_EXPORTER_OTLP_ENDPOINT }}
32 | FILE_SUFFIX: "latest"
33 |
34 | - name: Set last update date
35 | run: |
36 | current_day=$(date +"%Y-%m-%d")
37 | echo "${current_day}" > "last-update.txt"
38 |
39 | - name: Configure Git user
40 | run: |
41 | git config user.name "github-actions[bot]"
42 | git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
43 |
44 | - name: Push generated csv files to the repo
45 | uses: stefanzweifel/git-auto-commit-action@v5
46 | with:
47 | commit_message: "chore: update stats"
48 | commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
49 |
50 | - name: Upload results
51 | uses: actions/upload-artifact@v4
52 | with:
53 | name: generated-stats-csv
54 | path: |
55 | ./*.csv
56 | ./last-update.txt
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | *.csv
3 | .vscode
4 | .DS_Store
5 | .idea
6 |
7 | *.ipynb
8 |
9 | # Binaries for programs and plugins
10 | *.exe
11 | *.exe~
12 | *.dll
13 | *.so
14 | *.dylib
15 |
16 | # Test binary, built with `go test -c`
17 | *.test
18 | *.tmp
19 |
20 | # Output of the go coverage tool, specifically when used with LiteIDE
21 | *.out
22 |
23 | # Misc
24 | *.fiber.gz
25 | *.fasthttp.gz
26 | *.pprof
27 | *.workspace
28 |
29 | # Dependencies
30 | /vendor/
31 | vendor/
32 | vendor
33 | /Godeps/
34 |
35 | !/analysis-latest.csv
36 | !/dep-repo-latest.csv
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # awesome-go-repo-stats
2 |
3 | https://github.com/emanuelef/awesome-go-repo-stats/assets/48717/9876dac3-c75a-49c3-b979-db1b5df401e4
4 |
5 | ## Introduction
6 |
7 | This project offers a comprehensive analysis of repositories curated in the list found at [awesome-go](https://github.com/avelino/awesome-go). You can access the live deployment at [awesome-go-repo-stats](https://emanuelef.github.io/awesome-go-repo-stats), with daily updates to keep you informed.
8 |
9 |
10 |
11 | ## Main page
12 |
13 |
14 |
15 | | Field | Description |
16 | | --- | --- |
17 | | Stars | Total stars up to the latest update |
18 | | Days last commit | Number of days since the last commit, 0 indicates at least one commit in the last day |
19 | | Days last star | Number of days since the last star, a value of 0 indicates that the repository received at least one star within the last day |
20 | | Stars last 30d | Number of new stars in the last 30 days |
21 | | Stars last 7d | Number of new stars in the last 7 days |
22 | | New Stars 30d ‰ | New stars in the last 30 days / Total Stars |
23 | | Ment. users | Mentionable users |
24 | | Direct deps | Number of direct dependencies found in go.mod |
25 | | Created days ago | Number of days since repo creation |
26 | | Archived | Archived status od the project, if true the project has been archived |
27 | | Stars Timeline 30d | Link to a page with the daily new stars in the last 30 days |
28 |
29 | ## Stars Timeline 30d
30 |
31 |
32 |
33 |
34 | ## Contributing
35 |
36 | Contributions are welcome! Feel free to open an issue or create a pull request.
37 |
38 |
--------------------------------------------------------------------------------
/dep-repo-latest.csv:
--------------------------------------------------------------------------------
1 | dep,awesome_go_repos_using_dep
2 | go.uber.org/automaxprocs,27
3 | github.com/spf13/afero,31
4 | github.com/aws/aws-sdk-go-v2,40
5 | github.com/mattn/go-sqlite3,53
6 | github.com/grpc-ecosystem/go-grpc-middleware,23
7 | go.opentelemetry.io/otel/trace,58
8 | golang.org/x/time,54
9 | gonum.org/v1/gonum,23
10 | golang.org/x/sync,165
11 | github.com/gorilla/mux,63
12 | github.com/fatih/color,102
13 | go.opentelemetry.io/otel/exporters/otlp/otlptrace,23
14 | github.com/hashicorp/go-multierror,31
15 | github.com/PuerkitoBio/goquery,23
16 | golang.org/x/text,131
17 | github.com/gofrs/uuid,22
18 | github.com/go-chi/chi/v5,30
19 | golang.org/x/sys,147
20 | github.com/fsnotify/fsnotify,72
21 | github.com/gorilla/websocket,79
22 | github.com/tidwall/gjson,30
23 | github.com/golang/snappy,29
24 | gopkg.in/yaml.v3,151
25 | github.com/redis/go-redis/v9,38
26 | github.com/cespare/xxhash/v2,23
27 | golang.org/x/oauth2,90
28 | github.com/mitchellh/mapstructure,42
29 | k8s.io/client-go,34
30 | github.com/google/uuid,177
31 | modernc.org/sqlite,24
32 | github.com/BurntSushi/toml,42
33 | google.golang.org/grpc,117
34 | github.com/mitchellh/go-homedir,41
35 | github.com/spf13/viper,110
36 | go.opentelemetry.io/otel/sdk,51
37 | go.uber.org/zap,86
38 | github.com/dustin/go-humanize,53
39 | github.com/Azure/azure-sdk-for-go/sdk/azcore,21
40 | github.com/golang-jwt/jwt/v5,25
41 | github.com/urfave/cli/v2,36
42 | github.com/lib/pq,79
43 | github.com/spf13/cobra,205
44 | golang.org/x/exp,93
45 | golang.org/x/crypto,200
46 | github.com/google/go-cmp,118
47 | github.com/gin-gonic/gin,38
48 | github.com/rs/cors,23
49 | github.com/Masterminds/sprig/v3,26
50 | github.com/golang/mock,31
51 | go.opentelemetry.io/otel/metric,21
52 | golang.org/x/image,38
53 | github.com/valyala/fasthttp,23
54 | github.com/davecgh/go-spew,48
55 | github.com/DATA-DOG/go-sqlmock,35
56 | github.com/go-sql-driver/mysql,93
57 | go.uber.org/goleak,43
58 | github.com/pkg/errors,166
59 | go.uber.org/atomic,25
60 | github.com/docker/docker,43
61 | go.mongodb.org/mongo-driver,25
62 | golang.org/x/mod,46
63 | k8s.io/apimachinery,33
64 | github.com/pmezard/go-difflib,24
65 | go.etcd.io/bbolt,25
66 | github.com/mattn/go-colorable,34
67 | github.com/miekg/dns,26
68 | github.com/golang/protobuf,54
69 | github.com/aws/aws-sdk-go,56
70 | github.com/opentracing/opentracing-go,21
71 | github.com/aws/aws-sdk-go-v2/service/s3,26
72 | golang.org/x/net,217
73 | github.com/prometheus/client_golang,134
74 | github.com/go-git/go-git/v5,29
75 | github.com/jmoiron/sqlx,21
76 | github.com/sirupsen/logrus,96
77 | github.com/mattn/go-isatty,46
78 | github.com/docker/go-connections,25
79 | github.com/jackc/pgx/v5,40
80 | go.etcd.io/etcd/client/v3,29
81 | google.golang.org/api,61
82 | go.uber.org/mock,29
83 | github.com/Masterminds/semver/v3,22
84 | github.com/mattn/go-runewidth,35
85 | github.com/robfig/cron/v3,33
86 | github.com/cenkalti/backoff/v4,24
87 | gopkg.in/natefinch/lumberjack.v2,22
88 | github.com/golang-jwt/jwt/v4,22
89 | github.com/gogo/protobuf,26
90 | github.com/onsi/gomega,28
91 | github.com/go-playground/validator/v10,35
92 | gorm.io/gorm,26
93 | github.com/spf13/cast,28
94 | google.golang.org/protobuf,132
95 | k8s.io/api,31
96 | github.com/charmbracelet/lipgloss,21
97 | gopkg.in/yaml.v2,104
98 | github.com/shirou/gopsutil/v3,22
99 | github.com/olekukonko/tablewriter,36
100 | github.com/aws/aws-sdk-go-v2/credentials,24
101 | github.com/prometheus/client_model,31
102 | go.opentelemetry.io/otel,63
103 | cloud.google.com/go/storage,32
104 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc,30
105 | golang.org/x/tools,101
106 | golang.org/x/term,70
107 | github.com/json-iterator/go,38
108 | github.com/prometheus/common,29
109 | github.com/aws/aws-sdk-go-v2/config,38
110 | github.com/joho/godotenv,34
111 | github.com/klauspost/compress,58
112 | github.com/stretchr/testify,803
113 | github.com/spf13/pflag,88
114 | github.com/rs/zerolog,42
115 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/emanuelef/awesome-go-repo-stats
2 |
3 | go 1.23
4 |
5 | toolchain go1.23.0
6 |
7 | require (
8 | github.com/emanuelef/github-repo-activity-stats v0.2.42
9 | github.com/go-resty/resty/v2 v2.16.2
10 | github.com/joho/godotenv v1.5.1
11 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.57.0
12 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0
13 | go.opentelemetry.io/otel v1.32.0
14 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0
15 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0
16 | go.opentelemetry.io/otel/sdk v1.32.0
17 | go.opentelemetry.io/otel/trace v1.32.0
18 | golang.org/x/oauth2 v0.24.0
19 | )
20 |
21 | require (
22 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect
23 | github.com/felixge/httpsnoop v1.0.4 // indirect
24 | github.com/go-logr/logr v1.4.2 // indirect
25 | github.com/go-logr/stdr v1.2.2 // indirect
26 | github.com/google/uuid v1.6.0 // indirect
27 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect
28 | github.com/pelletier/go-toml v1.9.5 // indirect
29 | github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 // indirect
30 | github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
31 | go.opentelemetry.io/otel/metric v1.32.0 // indirect
32 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect
33 | golang.org/x/mod v0.22.0 // indirect
34 | golang.org/x/net v0.32.0 // indirect
35 | golang.org/x/sync v0.10.0 // indirect
36 | golang.org/x/sys v0.28.0 // indirect
37 | golang.org/x/text v0.21.0 // indirect
38 | google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
39 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
40 | google.golang.org/grpc v1.67.1 // indirect
41 | google.golang.org/protobuf v1.35.1 // indirect
42 | )
43 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
2 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5 | github.com/emanuelef/github-repo-activity-stats v0.2.42 h1:C7KoV963mkJpM5ZwllIalBhUscZ6EOG6wXLzxym9vfQ=
6 | github.com/emanuelef/github-repo-activity-stats v0.2.42/go.mod h1:Eu4nuRwAV64FrTrv/jnBfrZcLJoLdzYfUWZClcwJGgI=
7 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
8 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
9 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
10 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
11 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
12 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
13 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
14 | github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg=
15 | github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU=
16 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
17 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
18 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
19 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
20 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU=
21 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0=
22 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
23 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
24 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
25 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
26 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
27 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
28 | github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M=
29 | github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8=
30 | github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0=
31 | github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE=
32 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
33 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
34 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.57.0 h1:7F3XCD6WYzDkwbi8I8N+oYJWquPVScnRosKGgqjsR8c=
35 | go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.57.0/go.mod h1:Dk3C0BfIlZDZ5c6eVS7TYiH2vssuyUU3vUsgbrR+5V4=
36 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
37 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
38 | go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
39 | go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
40 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
41 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
42 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU=
43 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ=
44 | go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
45 | go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
46 | go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
47 | go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
48 | go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
49 | go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
50 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
51 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
52 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
53 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
54 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
55 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
56 | golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
57 | golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
58 | golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
59 | golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
60 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
61 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
62 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
63 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
64 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
65 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
66 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
67 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
68 | google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g=
69 | google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4=
70 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
71 | google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
72 | google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
73 | google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
74 | google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
75 | google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
76 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
77 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
78 |
--------------------------------------------------------------------------------
/last-update.txt:
--------------------------------------------------------------------------------
1 | 2025-06-04
2 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "context"
7 | "encoding/csv"
8 | "encoding/json"
9 | "fmt"
10 | "log"
11 | "net/http"
12 | "net/http/httptrace"
13 | "os"
14 | "regexp"
15 | "strings"
16 | "time"
17 |
18 | "github.com/emanuelef/awesome-go-repo-stats/otel_instrumentation"
19 | "github.com/emanuelef/github-repo-activity-stats/repostats"
20 | "github.com/emanuelef/github-repo-activity-stats/stats"
21 | "github.com/go-resty/resty/v2"
22 | _ "github.com/joho/godotenv/autoload"
23 | "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
24 | "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
25 | "go.opentelemetry.io/otel"
26 | "go.opentelemetry.io/otel/trace"
27 | "golang.org/x/oauth2"
28 | )
29 |
30 | const (
31 | AwesomeGoMarkdownUrl = "https://raw.githubusercontent.com/avelino/awesome-go/main/README.md"
32 | )
33 |
34 | func getEnv(key, defaultValue string) string {
35 | value := os.Getenv(key)
36 | if len(value) == 0 {
37 | return defaultValue
38 | }
39 | return value
40 | }
41 |
42 | func writeGoDepsMapFile(deps map[string]int) {
43 | currentTime := time.Now()
44 | outputFile, err := os.Create(fmt.Sprintf("dep-repo-%s.csv", getEnv("FILE_SUFFIX", (currentTime.Format("02-01-2006")))))
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 |
49 | defer outputFile.Close()
50 |
51 | csvWriter := csv.NewWriter(outputFile)
52 | defer csvWriter.Flush()
53 |
54 | headerRow := []string{
55 | "dep", "awesome_go_repos_using_dep",
56 | }
57 |
58 | err = csvWriter.Write(headerRow)
59 |
60 | if err != nil {
61 | log.Fatal(err)
62 | }
63 |
64 | for k, v := range deps {
65 | if v > 20 {
66 | err = csvWriter.Write([]string{
67 | k,
68 | fmt.Sprintf("%d", v),
69 | })
70 | if err != nil {
71 | log.Fatal(err)
72 | }
73 | }
74 | }
75 | }
76 |
77 | var tracer trace.Tracer
78 |
79 | func init() {
80 | tracer = otel.Tracer("github.com/emanuelef/awesome-go-repo-stats")
81 | }
82 |
83 | func main() {
84 | ctx := context.Background()
85 |
86 | starsHistory := map[string][]stats.StarsPerDay{}
87 | commitsHistory := map[string][]stats.CommitsPerDay{}
88 |
89 | tp, exp, err := otel_instrumentation.InitializeGlobalTracerProvider(ctx)
90 | // Handle shutdown to ensure all sub processes are closed correctly and telemetry is exported
91 | if err != nil {
92 | log.Fatalf("failed to initialize OpenTelemetry: %e", err)
93 | }
94 |
95 | ctx, span := tracer.Start(ctx, "fetch-all-stats")
96 |
97 | defer func() {
98 | fmt.Println("before End")
99 | span.End()
100 | time.Sleep(10 * time.Second)
101 | fmt.Println("before exp Shutdown")
102 | _ = exp.Shutdown(ctx)
103 | fmt.Println("before tp Shutdown")
104 | _ = tp.Shutdown(ctx)
105 | }()
106 |
107 | currentTime := time.Now()
108 | outputFile, err := os.Create(fmt.Sprintf("analysis-%s.csv", getEnv("FILE_SUFFIX", currentTime.Format("02-01-2006"))))
109 | if err != nil {
110 | log.Fatal(err)
111 | }
112 |
113 | defer outputFile.Close()
114 |
115 | csvWriter := csv.NewWriter(outputFile)
116 | defer csvWriter.Flush()
117 |
118 | headerRow := []string{
119 | "repo", "stars", "new-stars-last-30d", "new-stars-last-14d",
120 | "new-stars-last-7d", "new-stars-last-24H", "stars-per-mille-30d",
121 | "days-last-star", "days-last-commit",
122 | "days-since-creation", "mentionable-users",
123 | "language",
124 | "archived", "dependencies",
125 | "main-category", "sub-category",
126 | "min-go-version",
127 | "liveness",
128 | "unique-contributors",
129 | "new-commits-last-30d",
130 | }
131 |
132 | err = csvWriter.Write(headerRow)
133 |
134 | if err != nil {
135 | log.Fatal(err)
136 | }
137 |
138 | depsUse := map[string]int{}
139 |
140 | tokenSource := oauth2.StaticTokenSource(
141 | &oauth2.Token{AccessToken: os.Getenv("PAT")},
142 | )
143 |
144 | oauthClient := oauth2.NewClient(context.Background(), tokenSource)
145 | // client := repostats.NewClient(&oauthClient.Transport)
146 | client := repostats.NewClientGQL(oauthClient)
147 |
148 | restyClient := resty.NewWithClient(
149 | &http.Client{
150 | Transport: otelhttp.NewTransport(http.DefaultTransport,
151 | otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
152 | return otelhttptrace.NewClientTrace(ctx)
153 | })),
154 | },
155 | )
156 |
157 | restyReq := restyClient.R()
158 | restyReq.SetContext(ctx)
159 | resp, err := restyReq.Get(AwesomeGoMarkdownUrl)
160 | i := 0
161 | if err == nil {
162 | reader := bytes.NewReader([]byte(resp.Body()))
163 | scanner := bufio.NewScanner(reader)
164 |
165 | // - [resty](https://github.com/go-resty/resty) - Simple HTTP and REST client for Go inspired by Ruby rest-client.
166 | exp := regexp.MustCompile(`- \[([^\]]+)]\(https:\/\/github\.com\/([^\/\s]+\/[^\/\s]+)\) - .*`)
167 |
168 | mainCategory := "General"
169 | subCategory := ""
170 |
171 | for scanner.Scan() {
172 | line := scanner.Text()
173 |
174 | if strings.HasPrefix(line, "## ") {
175 | fmt.Println(line)
176 | cleanedLine := strings.TrimPrefix(line, "## ")
177 |
178 | if mainCategory != cleanedLine {
179 | subCategory = ""
180 | mainCategory = cleanedLine
181 | }
182 | }
183 |
184 | if strings.HasPrefix(line, "### ") {
185 | fmt.Println(line)
186 | subCategory = strings.TrimPrefix(line, "### ")
187 | }
188 |
189 | subMatch := exp.FindStringSubmatch(line)
190 | if len(subMatch) >= 2 {
191 | repo := subMatch[2]
192 |
193 | fmt.Printf("repo : %d %s\n", i, repo)
194 | i += 1
195 |
196 | result, err := client.GetAllStats(ctx, repo)
197 | // if there is any error fetching any repo stop the update
198 | if err != nil {
199 | // retry once
200 | fmt.Println("retrying after 5 minutes")
201 | time.Sleep(5 * time.Minute)
202 | result, err = client.GetAllStats(ctx, repo)
203 | /*
204 | if err != nil {
205 | log.Fatal(err)
206 | }
207 | */
208 | }
209 |
210 | if err == nil {
211 | daysSinceLastStar := int(currentTime.Sub(result.LastStarDate).Hours() / 24)
212 | daysSinceLastCommit := int(currentTime.Sub(result.LastCommitDate).Hours() / 24)
213 | daysSinceCreation := int(currentTime.Sub(result.CreatedAt).Hours() / 24)
214 | err = csvWriter.Write([]string{
215 | repo,
216 | fmt.Sprintf("%d", result.Stars),
217 | fmt.Sprintf("%d", result.StarsHistory.AddedLast30d),
218 | fmt.Sprintf("%d", result.StarsHistory.AddedLast14d),
219 | fmt.Sprintf("%d", result.StarsHistory.AddedLast7d),
220 | fmt.Sprintf("%d", result.StarsHistory.AddedLast24H),
221 | fmt.Sprintf("%.3f", result.StarsHistory.AddedPerMille30d),
222 | fmt.Sprintf("%d", daysSinceLastStar),
223 | fmt.Sprintf("%d", daysSinceLastCommit),
224 | fmt.Sprintf("%d", daysSinceCreation),
225 | fmt.Sprintf("%d", result.MentionableUsers),
226 | result.Language,
227 | fmt.Sprintf("%t", result.Archived),
228 | fmt.Sprintf("%d", len(result.DirectDeps)),
229 | fmt.Sprintf(mainCategory),
230 | fmt.Sprintf(subCategory),
231 | fmt.Sprintf(result.GoVersion),
232 | fmt.Sprintf("%.3f", result.LivenessScore),
233 | fmt.Sprintf("%d", result.DifferentAuthors),
234 | fmt.Sprintf("%d", result.CommitsHistory.AddedLast30d),
235 | })
236 |
237 | if err != nil {
238 | log.Fatal(err)
239 | }
240 |
241 | if len(result.DirectDeps) > 0 {
242 | for _, dep := range result.DirectDeps {
243 | depsUse[dep] += 1
244 | }
245 | }
246 |
247 | starsHistory[repo] = result.StarsTimeline
248 | commitsHistory[repo] = result.CommitsTimeline
249 |
250 | // wait to avoid hitting 5k rate limit
251 | if i%100 == 0 {
252 | time.Sleep(1 * time.Minute)
253 | }
254 |
255 | }
256 |
257 | }
258 | }
259 |
260 | writeGoDepsMapFile(depsUse)
261 |
262 | jsonData, _ := json.MarshalIndent(starsHistory, "", " ")
263 | _ = os.WriteFile("stars-history-30d.json", jsonData, 0o644)
264 |
265 | commitsJsonData, _ := json.MarshalIndent(commitsHistory, "", " ")
266 | _ = os.WriteFile("commits-history-30d.json", commitsJsonData, 0o644)
267 | }
268 |
269 | elapsed := time.Since(currentTime)
270 | log.Printf("Took %s\n", elapsed)
271 | }
272 |
--------------------------------------------------------------------------------
/otel_instrumentation/opentelemetry_setup.go:
--------------------------------------------------------------------------------
1 | package otel_instrumentation
2 |
3 | import (
4 | "context"
5 | "log"
6 |
7 | _ "github.com/joho/godotenv/autoload"
8 |
9 | "go.opentelemetry.io/otel"
10 | "go.opentelemetry.io/otel/attribute"
11 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
13 | "go.opentelemetry.io/otel/propagation"
14 | "go.opentelemetry.io/otel/sdk/resource"
15 | sdktrace "go.opentelemetry.io/otel/sdk/trace"
16 | semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
17 | )
18 |
19 | // Used to initialise the global OpenTelemetry trace provider and exporter
20 | func InitializeGlobalTracerProvider(ctx context.Context) (*sdktrace.TracerProvider, *otlptrace.Exporter, error) {
21 | // Configure a new OTLP exporter using environment variables for sending data to Honeycomb over gRPC
22 | clientOTel := otlptracegrpc.NewClient()
23 | exp, err := otlptrace.New(ctx, clientOTel)
24 | if err != nil {
25 | log.Fatalf("failed to initialize exporter: %e", err)
26 | }
27 |
28 | resource, rErr := resource.Merge(
29 | resource.Default(),
30 | resource.NewWithAttributes(
31 | semconv.SchemaURL,
32 | attribute.String("environment", "test"),
33 | ),
34 | )
35 |
36 | if rErr != nil {
37 | panic(rErr)
38 | }
39 |
40 | // Create a new tracer provider with a batch span processor and the otlp exporter
41 | tp := sdktrace.NewTracerProvider(
42 | sdktrace.WithBatcher(exp),
43 | sdktrace.WithSampler(sdktrace.AlwaysSample()),
44 | sdktrace.WithResource(resource),
45 | )
46 |
47 | // Register the global Tracer provider
48 | otel.SetTracerProvider(tp)
49 |
50 | // Register the W3C trace context and baggage propagators so data is propagated across services/processes
51 | otel.SetTextMapPropagator(
52 | propagation.NewCompositeTextMapPropagator(
53 | propagation.TraceContext{},
54 | propagation.Baggage{},
55 | ),
56 | )
57 |
58 | return tp, exp, nil
59 | }
60 |
--------------------------------------------------------------------------------
/update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | git pull
4 | go get -u
5 | gofumpt -l -w ../
6 | go mod tidy
7 |
8 | (cd ./website && ncu -u && npm i)
9 |
--------------------------------------------------------------------------------
/website/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:@typescript-eslint/recommended',
7 | 'plugin:react-hooks/recommended',
8 | ],
9 | ignorePatterns: ['dist', '.eslintrc.cjs'],
10 | parser: '@typescript-eslint/parser',
11 | plugins: ['react-refresh'],
12 | rules: {
13 | 'react-refresh/only-export-components': [
14 | 'warn',
15 | { allowConstantExport: true },
16 | ],
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # React + TypeScript + Vite
2 |
3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4 |
5 | Currently, two official plugins are available:
6 |
7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9 |
10 | ## Expanding the ESLint configuration
11 |
12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
13 |
14 | - Configure the top-level `parserOptions` property like this:
15 |
16 | ```js
17 | parserOptions: {
18 | ecmaVersion: 'latest',
19 | sourceType: 'module',
20 | project: ['./tsconfig.json', './tsconfig.node.json'],
21 | tsconfigRootDir: __dirname,
22 | },
23 | ```
24 |
25 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
26 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
27 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
28 |
--------------------------------------------------------------------------------
/website/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Awesome Go Stats
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "private": true,
4 | "version": "0.0.1",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "start": "vite --open",
10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
11 | "preview": "vite preview"
12 | },
13 | "dependencies": {
14 | "@emotion/react": "^11.13.5",
15 | "@emotion/styled": "^11.13.0",
16 | "@mui/icons-material": "^5.16.7",
17 | "@mui/material": "^5.16.7",
18 | "@mui/x-data-grid": "^7.23.1",
19 | "@nivo/bar": "^0.87.0",
20 | "@nivo/waffle": "^0.87.0",
21 | "@types/plotly.js": "^2.33.1",
22 | "@uiw/react-github-corners": "^1.5.16",
23 | "fusioncharts": "^4.0.2",
24 | "papaparse": "^5.4.1",
25 | "plotly.js": "^2.34.0",
26 | "plotly.js-dist": "^2.35.2",
27 | "plotly.js-dist-min": "^2.35.2",
28 | "react": "^18.3.1",
29 | "react-dom": "^18.3.1",
30 | "react-fusioncharts": "^4.1.0",
31 | "react-github-btn": "^1.4.0",
32 | "react-plotly.js": "^2.6.0",
33 | "react-pro-sidebar": "^1.1.0",
34 | "react-router-dom": "^7.0.1"
35 | },
36 | "devDependencies": {
37 | "@types/react": "^18.3.12",
38 | "@types/react-dom": "^18.3.0",
39 | "@typescript-eslint/eslint-plugin": "^8.18.0",
40 | "@typescript-eslint/parser": "^8.16.0",
41 | "@vitejs/plugin-react": "^4.3.1",
42 | "eslint-plugin-react-hooks": "^5.1.0",
43 | "eslint-plugin-react-refresh": "^0.4.9",
44 | "typescript": "^5.5.4",
45 | "vite": "^6.0.3"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/website/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/3f9bc752ca422d3ce032adb9e8844719f224cdef/website/public/favicon.png
--------------------------------------------------------------------------------
/website/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/3f9bc752ca422d3ce032adb9e8844719f224cdef/website/src/App.css
--------------------------------------------------------------------------------
/website/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 | import { useParams, useNavigate } from "react-router-dom";
3 | // @ts-ignore
4 | import Papa from "papaparse";
5 | import "./App.css";
6 | import { DataGrid, GridColDef } from "@mui/x-data-grid";
7 | import Linkweb from "@mui/material/Link";
8 | import TextField from "@mui/material/TextField";
9 | import Autocomplete from "@mui/material/Autocomplete";
10 |
11 | import { Sidebar, Menu, MenuItem } from "react-pro-sidebar";
12 | import { Routes, Route, Link } from "react-router-dom";
13 |
14 | import TimeSeriesChart from "./TimeSeriesChart";
15 | import DepsChart from "./DepsChart";
16 | import BubbleChart from "./BubbleChart";
17 |
18 | import MenuRoundedIcon from "@mui/icons-material/MenuRounded";
19 | import TableViewRounded from "@mui/icons-material/TableViewRounded";
20 | import ViewListRoundedIcon from "@mui/icons-material/ViewListRounded";
21 | import BarChartRoundedIcon from "@mui/icons-material/BarChartRounded";
22 | import TimelineRoundedIcon from "@mui/icons-material/TimelineRounded";
23 | import LibraryBooksRoundedIcon from "@mui/icons-material/LibraryBooksRounded";
24 | import ViewModuleRoundedIcon from "@mui/icons-material/ViewModuleRounded";
25 | import BubbleChartRoundedIcon from "@mui/icons-material/BubbleChartRounded";
26 |
27 | import GitHubButton from "react-github-btn";
28 |
29 | // Import the Header component
30 | import Header from "./Header";
31 |
32 | import { ThemeProvider, createTheme } from "@mui/material/styles";
33 | import CssBaseline from "@mui/material/CssBaseline";
34 |
35 | const darkTheme = createTheme({
36 | palette: {
37 | mode: "dark",
38 | },
39 | });
40 |
41 | /*
42 | archived
43 | "false"
44 | days-last-commit
45 | "151"
46 | days-last-star
47 | "5"
48 | days-since-creation
49 | "3961"
50 | dependencies
51 | "5"
52 | language
53 | "Go"
54 | mentionable-users
55 | "8"
56 | new-stars-last-7d
57 | "1"
58 | new-stars-last-14d
59 | "5"
60 | new-stars-last-24H
61 | "0"
62 | new-stars-last-30d
63 | "7"
64 | repo
65 | "mewkiz/flac"
66 | stars
67 | "262"
68 | stars-per-mille-30d
69 | "26.718"*/
70 |
71 | const GitHubURL = "https://github.com/";
72 |
73 | const csvURL =
74 | "https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main/analysis-latest.csv";
75 |
76 | const lastUpdateURL =
77 | "https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main/last-update.txt";
78 |
79 | const fullStarsHistoryURL =
80 | "https://emanuelef.github.io/daily-stars-explorer/#/";
81 |
82 | const TimelineMetrics = ["Stars", "Commits"];
83 |
84 | const extractVersionNumber = (value) => {
85 | const match = value.match(/\d+(\.\d+)+(\.\d+)?/);
86 | return match ? match[0] : null;
87 | };
88 |
89 | const compareSemanticVersions = (versionA, versionB) => {
90 | const semVerA = versionA.split(".");
91 | const semVerB = versionB.split(".");
92 |
93 | for (let i = 0; i < Math.min(semVerA.length, semVerB.length); i++) {
94 | if (semVerA[i] !== semVerB[i]) {
95 | return semVerA[i] - semVerB[i];
96 | }
97 | }
98 |
99 | return semVerA.length - semVerB.length;
100 | };
101 |
102 | const getColorFromValue = (value) => {
103 | // Normalize the value to a scale from 0 to 1
104 | const normalizedValue = value / 100;
105 |
106 | // Define the colors for the gradient
107 | const colors = [
108 | { percent: 0, color: "#D9534F" }, // Adjusted Red
109 | { percent: 0.5, color: "#FFA500" }, // Orange
110 | { percent: 1, color: "#5CB85C" }, // Adjusted Green
111 | ];
112 |
113 | // Find the two colors to interpolate between
114 | let startColor, endColor;
115 | for (let i = 0; i < colors.length - 1; i++) {
116 | if (
117 | normalizedValue >= colors[i].percent &&
118 | normalizedValue <= colors[i + 1].percent
119 | ) {
120 | startColor = colors[i];
121 | endColor = colors[i + 1];
122 | break;
123 | }
124 | }
125 |
126 | // Interpolate between the two colors
127 | const ratio =
128 | (normalizedValue - startColor.percent) /
129 | (endColor.percent - startColor.percent);
130 | const rgbColor = interpolateColor(startColor.color, endColor.color, ratio);
131 |
132 | return rgbColor;
133 | };
134 |
135 | const interpolateColor = (startColor, endColor, ratio) => {
136 | const startRGB = hexToRgb(startColor);
137 | const endRGB = hexToRgb(endColor);
138 |
139 | const interpolatedRGB = startRGB.map((channel, index) =>
140 | Math.round(channel + ratio * (endRGB[index] - channel))
141 | );
142 |
143 | return `rgb(${interpolatedRGB.join(", ")})`;
144 | };
145 |
146 | const hexToRgb = (hex) => {
147 | const hexDigits = hex.slice(1).match(/.{1,2}/g);
148 | return hexDigits.map((value) => parseInt(value, 16));
149 | };
150 |
151 | const calculateAge = (days) => {
152 | const years = Math.floor(days / 365);
153 | const months = Math.floor((days % 365) / 30);
154 | const remainingDays = days % 30;
155 |
156 | return `${years !== 0 ? `${years}y ` : ""}${
157 | months !== 0 ? `${months}m ` : ""
158 | }${remainingDays}d`;
159 | };
160 |
161 | const columns: GridColDef[] = [
162 | {
163 | field: "repo",
164 | headerName: "Repo",
165 | width: 220,
166 | renderCell: (params) => (
167 |
168 | {params.value}
169 |
170 | ),
171 | },
172 | {
173 | field: "stars",
174 | headerName: "Stars",
175 | width: 90,
176 | valueGetter: (val) => parseInt(val),
177 | },
178 | {
179 | field: "days-last-commit",
180 | headerName: "Days last commit",
181 | width: 130,
182 | valueGetter: (val) => parseInt(val),
183 | },
184 | {
185 | field: "days-last-star",
186 | headerName: "Days last star",
187 | width: 110,
188 | valueGetter: (val) => parseInt(val),
189 | },
190 | {
191 | field: "new-stars-last-30d",
192 | headerName: "Stars last 30d",
193 | width: 110,
194 | valueGetter: (val) => parseInt(val),
195 | },
196 | {
197 | field: "new-stars-last-7d",
198 | headerName: "Stars last 7d",
199 | width: 110,
200 | valueGetter: (val) => parseInt(val),
201 | },
202 | {
203 | field: "stars-per-mille-30d",
204 | headerName: "New Stars 30d ‰",
205 | width: 130,
206 | valueGetter: (val) => parseFloat(val),
207 | },
208 | {
209 | field: "new-commits-last-30d",
210 | headerName: "Commits 30d",
211 | width: 100,
212 | valueGetter: (val) => parseInt(val),
213 | },
214 | {
215 | field: "unique-contributors",
216 | headerName: "Commits Authors 30d",
217 | width: 100,
218 | valueGetter: (val) => parseInt(val),
219 | },
220 | {
221 | field: "mentionable-users",
222 | headerName: "Ment. users",
223 | width: 110,
224 | valueGetter: (val) => parseInt(val),
225 | },
226 | {
227 | field: "dependencies",
228 | headerName: "Direct deps",
229 | width: 100,
230 | valueGetter: (val) => parseInt(val),
231 | },
232 | {
233 | field: "days-since-creation",
234 | headerName: "Age",
235 | width: 130,
236 | valueGetter: (val) => parseInt(val),
237 | renderCell: (params) => calculateAge(params.value),
238 | },
239 | {
240 | field: "min-go-version",
241 | headerName: "Min Go version",
242 | width: 80,
243 | valueGetter: (val) => val,
244 | sortComparator: (a, b) => {
245 | // Extract the version numbers
246 | const versionA = extractVersionNumber(a);
247 | const versionB = extractVersionNumber(b);
248 |
249 | // Use a custom comparison for semantic versions
250 | if (versionA && versionB) {
251 | return compareSemanticVersions(versionA, versionB);
252 | }
253 |
254 | // If the versions are not in the correct format, use a basic string comparison
255 | return a.localeCompare(b);
256 | },
257 | },
258 | {
259 | field: "archived",
260 | headerName: "Archived",
261 | width: 100,
262 | renderCell: (params) => (
263 |
264 | {params.value}
265 |
266 | ),
267 | },
268 | {
269 | headerName: "Stars Timeline 30d",
270 | width: 120,
271 | renderCell: (params) => (
272 | link
273 | ),
274 | },
275 | {
276 | field: "liveness",
277 | headerName: "Liveness",
278 | width: 120,
279 | valueGetter: (val) => parseFloat(val),
280 | renderCell: (params) => {
281 | const value = params.value;
282 | const color = getColorFromValue(value);
283 |
284 | return (
285 |
297 | {`${value}%`}
298 |
299 | );
300 | },
301 | },
302 | ];
303 |
304 | // https://emanuelef.github.io/awesome-go-repo-stats/#/starstimeline/mewkiz/flac
305 |
306 | // https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main/analysis-latest.csv
307 |
308 | function App() {
309 | const fetchReposData = () => {
310 | fetch(csvURL)
311 | .then((response) => response.text())
312 | .then((text) => Papa.parse(text, { header: true, skipEmptyLines: true }))
313 | .then(function (result) {
314 | console.log(result);
315 | setDataRows(result.data);
316 | setFilteredDataRows(result.data);
317 | })
318 | .catch((e) => {
319 | console.error(`An error occurred: ${e}`);
320 | });
321 | };
322 |
323 | const fetchLastUpdate = () => {
324 | fetch(lastUpdateURL)
325 | .then((response) => response.text())
326 | .then(function (dateString) {
327 | console.log(dateString);
328 | const parts = dateString.split("-");
329 | if (parts.length === 3) {
330 | const year = parseInt(parts[0]);
331 | const month = parseInt(parts[1]) - 1; // Months are 0-indexed
332 | const day = parseInt(parts[2]);
333 | const options = { year: "numeric", month: "long", day: "numeric" };
334 | const formattedDate = new Date(year, month, day).toLocaleDateString(
335 | "en-US",
336 | options
337 | );
338 | setLastUpdate(formattedDate);
339 | }
340 | })
341 | .catch((e) => {
342 | console.error(`An error occurred: ${e}`);
343 | });
344 | };
345 |
346 | const [dataRows, setDataRows] = useState([]);
347 | const [filteredDataRows, setFilteredDataRows] = useState([]);
348 | const [selectedRepo, setSelectedRepo] = useState("kubernetes/kubernetes");
349 | const [collapsed, setCollapsed] = useState(true);
350 | const [lastUpdate, setLastUpdate] = useState("Unknown");
351 | const [mainCategory, setMainCategory] = useState("All");
352 | const [subCategory, setSubCategory] = useState("All");
353 | const [subCategories, setSubCategories] = useState([]);
354 | const [selectedTimelineMetric, setSelectedTimelineMetric] = useState(
355 | TimelineMetrics[0]
356 | );
357 |
358 | const navigate = useNavigate();
359 |
360 | useEffect(() => {
361 | fetchReposData();
362 | fetchLastUpdate();
363 | }, []);
364 |
365 | useEffect(() => {
366 | const subCategories = [
367 | ...new Set(
368 | dataRows
369 | .filter((el) => el["main-category"] === mainCategory)
370 | .map((el) => el["sub-category"])
371 | ),
372 | ];
373 | setSubCategories(subCategories);
374 |
375 | if (mainCategory === "All") {
376 | setFilteredDataRows(dataRows);
377 | } else {
378 | setFilteredDataRows(
379 | dataRows.filter((el) => el["main-category"] === mainCategory)
380 | );
381 | }
382 | }, [mainCategory]);
383 |
384 | useEffect(() => {
385 | if (subCategory === "All") {
386 | setFilteredDataRows(
387 | dataRows.filter((el) => el["main-category"] === mainCategory)
388 | );
389 | } else {
390 | setFilteredDataRows(
391 | dataRows.filter((el) => el["sub-category"] === subCategory)
392 | );
393 | }
394 | }, [subCategory]);
395 |
396 | const Table = () => {
397 | return (
398 | <>
399 |
407 |
el["main-category"]))]}
412 | renderInput={(params) => (
413 |
424 | )}
425 | value={mainCategory}
426 | onChange={(e, v, reason) => {
427 | if (reason === "clear") {
428 | setMainCategory("All");
429 | } else {
430 | setMainCategory(v);
431 | }
432 | }}
433 | />
434 | (
440 |
451 | )}
452 | value={subCategory}
453 | onChange={(e, v, reason) => {
454 | if (reason === "clear") {
455 | setSubCategory("All");
456 | } else {
457 | setSubCategory(v);
458 | }
459 | }}
460 | />
461 |
462 |
463 | row.repo}
465 | rows={filteredDataRows}
466 | columns={columns}
467 | rowHeight={30}
468 | initialState={{
469 | pagination: {
470 | paginationModel: { page: 0, pageSize: 25 },
471 | },
472 | sorting: {
473 | sortModel: [{ field: "stars-per-mille-30d", sort: "desc" }],
474 | },
475 | }}
476 | pageSizeOptions={[5, 10]}
477 | />
478 |
479 | >
480 | );
481 | };
482 |
483 | const StarsTimeline = () => {
484 | const { user, repository } = useParams();
485 |
486 | useEffect(() => {
487 | console.log(user + "/" + repository);
488 | setSelectedRepo(user + "/" + repository);
489 | }, []);
490 |
491 | return (
492 | <>
493 |
496 |
{
501 | return { label: el.repo };
502 | })}
503 | renderInput={(params) => (
504 |
515 | )}
516 | value={selectedRepo}
517 | onChange={(e, v, reason) => {
518 | if (reason === "clear") {
519 | setSelectedRepo("kubernetes/kubernetes");
520 | navigate(`/starstimeline/kubernetes/kubernetes`, {
521 | replace: false,
522 | });
523 | } else {
524 | setSelectedRepo(v?.label);
525 | navigate(`/starstimeline/${v?.label}`, {
526 | replace: false,
527 | });
528 | }
529 | }}
530 | />
531 | (
539 |
545 | )}
546 | value={selectedTimelineMetric}
547 | onChange={(e, v, reason) => {
548 | if (reason === "clear") {
549 | setSelectedTimelineMetric(TimelineMetrics[0]);
550 | } else {
551 | setSelectedTimelineMetric(v);
552 | }
553 | }}
554 | />
555 |
562 |
567 | Full Stars History
568 |
569 |
570 |
571 | >
572 | );
573 | };
574 |
575 | return (
576 |
577 |
578 |
579 |
584 |
638 |
639 |
640 |
641 |
642 | } />
643 | } />
644 | } />
645 | }
648 | />
649 | }
652 | />
653 |
654 |
655 |
656 |
657 | );
658 | }
659 |
660 | export default App;
661 |
--------------------------------------------------------------------------------
/website/src/BubbleChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Plot from "react-plotly.js";
3 | import Autocomplete from "@mui/material/Autocomplete";
4 | import TextField from "@mui/material/TextField";
5 | import Papa from "papaparse";
6 |
7 | const fullStarsHistoryURL =
8 | "https://emanuelef.github.io/daily-stars-explorer/#";
9 |
10 | const csvURL =
11 | "https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main/analysis-latest.csv";
12 |
13 | const logBase = (n, base) => Math.log(n) / Math.log(base);
14 |
15 | const clickActions = [
16 | { label: "GH Repo", action: "gh" },
17 | { label: "Last 30d stars", action: "30d" },
18 | { label: "Full Star history", action: "full" },
19 | ];
20 |
21 | const bubbleColour = [
22 | { label: "Same", metric: "same" },
23 | { label: "Liveness", metric: "liveness" },
24 | ];
25 |
26 | const axisMetrics = [
27 | { label: "Stars Last 7 Days", metric: "new-stars-last-7d" },
28 | { label: "Stars Last 14 Days", metric: "new-stars-last-14d" },
29 | { label: "Stars Last 30 Days", metric: "new-stars-last-30d" },
30 | { label: "Mentionable Users", metric: "mentionable-users" },
31 | { label: "Total Stars", metric: "stars" },
32 | { label: "New Stars 30d‰", metric: "stars-per-mille-30d" },
33 | { label: "Age", metric: "days-since-creation" },
34 | { label: "Unique contributors 30d", metric: "unique-contributors" },
35 | { label: "Commits Last 30 Days", metric: "new-commits-last-30d" },
36 | ];
37 |
38 | const sizeMetrics = [
39 | { label: "Total Stars", metric: "stars" },
40 | { label: "Same", metric: "same" },
41 | { label: "Stars Last 30 Days", metric: "new-stars-last-30d" },
42 | { label: "Commits Last 30 Days", metric: "new-commits-last-30d" },
43 | { label: "Unique authors Last 30 Days", metric: "unique-contributors" },
44 | ];
45 |
46 | const formatStars = (stars) => {
47 | if (stars >= 1000) {
48 | return `${(stars / 1000).toFixed(1)}k`;
49 | } else {
50 | return stars.toString();
51 | }
52 | };
53 |
54 | const calculateAge = (days) => {
55 | const years = Math.floor(days / 365);
56 | const months = Math.floor((days % 365) / 30);
57 | const remainingDays = days % 30;
58 |
59 | return `${years !== 0 ? `${years}y ` : ""}${
60 | months !== 0 ? `${months}m ` : ""
61 | }${remainingDays}d`;
62 | };
63 |
64 | const getColorFromValue = (value) => {
65 | // Normalize the value to a scale from 0 to 1
66 | const normalizedValue = value / 100;
67 |
68 | // Define the colors for the gradient
69 | const colors = [
70 | { percent: 0, color: "#D9534F" }, // Adjusted Red
71 | { percent: 0.5, color: "#FFA500" }, // Orange
72 | { percent: 1, color: "#5CB85C" }, // Adjusted Green
73 | ];
74 |
75 | // Find the two colors to interpolate between
76 | let startColor, endColor;
77 | for (let i = 0; i < colors.length - 1; i++) {
78 | if (
79 | normalizedValue >= colors[i].percent &&
80 | normalizedValue <= colors[i + 1].percent
81 | ) {
82 | startColor = colors[i];
83 | endColor = colors[i + 1];
84 | break;
85 | }
86 | }
87 |
88 | // Interpolate between the two colors
89 | const ratio =
90 | (normalizedValue - startColor.percent) /
91 | (endColor.percent - startColor.percent);
92 | const rgbColor = interpolateColor(startColor.color, endColor.color, ratio);
93 |
94 | console.log(value);
95 | console.log(rgbColor);
96 |
97 | return rgbColor;
98 | };
99 |
100 | const interpolateColor = (startColor, endColor, ratio) => {
101 | const startRGB = hexToRgb(startColor);
102 | const endRGB = hexToRgb(endColor);
103 |
104 | const interpolatedRGB = startRGB.map((channel, index) =>
105 | Math.round(channel + ratio * (endRGB[index] - channel))
106 | );
107 |
108 | return `rgb(${interpolatedRGB.join(", ")})`;
109 | };
110 |
111 | const hexToRgb = (hex) => {
112 | const hexDigits = hex.slice(1).match(/.{1,2}/g);
113 | return hexDigits.map((value) => parseInt(value, 16));
114 | };
115 |
116 | const mapLivenessToColor = (liveness) => {
117 | return getColorFromValue(liveness) || "rgb(0, 0, 0)"; // Default to black if not found
118 | };
119 |
120 | const BubbleChart = ({ dataRows }) => {
121 | const [maxDaysLastCommit, setMaxDaysLastCommit] = useState("30");
122 | const [minStars, setMinStars] = useState("10");
123 | const [minMentionableUsers, setMinMentionableUsers] = useState("1");
124 | const [data, setData] = useState([]);
125 | const [selectedAction, setSelectedAction] = useState(clickActions[0].action);
126 |
127 | const [selectedXAxis, setSelectedXAxis] = useState(axisMetrics[0]);
128 | const [selectedYAxis, setSelectedYAxis] = useState(axisMetrics[3]);
129 |
130 | const [selectedSize, setSelectedSize] = useState(sizeMetrics[0]);
131 |
132 | const [selectedBubbleColour, setSelectedBubbleColour] = useState(
133 | bubbleColour[1]
134 | );
135 |
136 | const handleInputChange = (event, setStateFunction) => {
137 | const inputText = event.target.value;
138 |
139 | // Use a regular expression to check if the input contains only digits
140 | if (/^\d*$/.test(inputText)) {
141 | setStateFunction(inputText);
142 | }
143 | };
144 |
145 | const handleBubbleClick = (event) => {
146 | const pointIndex = event.points[0].pointIndex;
147 | const clickedRepo = event.points[0].data.repo[pointIndex];
148 |
149 | let url = `https://github.com/${clickedRepo}`;
150 |
151 | switch (selectedAction) {
152 | case "gh":
153 | window.open(url, "_blank");
154 | break;
155 |
156 | case "30d":
157 | url = `./#/starstimeline/${clickedRepo}`;
158 | window.location.href = url;
159 | break;
160 |
161 | case "full":
162 | url = `${fullStarsHistoryURL}/${clickedRepo}`;
163 | window.open(url, "_blank");
164 | break;
165 |
166 | default:
167 | window.open(url, "_blank");
168 | break;
169 | }
170 | };
171 |
172 | const getSize = (data) => {
173 | switch (selectedSize.metric) {
174 | case "stars":
175 | return data.map((row) => Math.sqrt(row[selectedSize.metric]) * 7);
176 | case "same":
177 | return data.map((row) => 600);
178 | case "liveness":
179 | return data.map((row) => row[selectedSize.metric] * 10);
180 | case "new-commits-last-30d":
181 | return data.map((row) => Math.sqrt(row[selectedSize.metric]) * 7);
182 | case "unique-contributors":
183 | return data.map((row) => Math.sqrt(row[selectedSize.metric]) * 12);
184 | case "new-stars-last-30d":
185 | return data.map((row) => Math.sqrt(row[selectedSize.metric]) * 9);
186 | default:
187 | return data.map((row) => 600);
188 | }
189 | };
190 |
191 | const getSizeRef = (metric) => {
192 | switch (metric) {
193 | case "new-commits-last-30d":
194 | return 2.0;
195 | case "unique-contributors":
196 | return 1.7;
197 | case "new-stars-last-30d":
198 | return 3.2;
199 | default:
200 | return 20.03;
201 | }
202 | };
203 |
204 | const buildChartData = (dataRows) => {
205 | let updatedData = [];
206 |
207 | dataRows.forEach((element) => {
208 | if (
209 | parseInt(element["days-last-commit"]) < parseInt(maxDaysLastCommit) &&
210 | parseInt(element["stars"]) > parseInt(minStars) &&
211 | parseInt(element["mentionable-users"]) > parseInt(minMentionableUsers)
212 | ) {
213 | updatedData.push(element);
214 | }
215 | });
216 |
217 | const trace = {
218 | x: updatedData.map((row) => row[selectedXAxis.metric]),
219 | y: updatedData.map((row) => row[selectedYAxis.metric]),
220 | repo: updatedData.map((row) => `${row.repo}`),
221 | text: updatedData.map(
222 | (row) =>
223 | `${row.repo}
Total Stars: ${formatStars(
224 | row.stars
225 | )}
Last commit: ${
226 | row["days-last-commit"]
227 | } days ago
Age: ${calculateAge(
228 | row["days-since-creation"]
229 | )}
Commits last 30d: ${
230 | row["new-commits-last-30d"]
231 | }
Unique authors last 30d: ${
232 | row["unique-contributors"]
233 | }
New stars last 30d: ${row["new-stars-last-30d"]}`
234 | ),
235 | mode: "markers",
236 | marker: {
237 | size: getSize(updatedData),
238 | sizemode: "diameter",
239 | sizeref: getSizeRef(selectedSize.metric),
240 | color:
241 | selectedBubbleColour.metric === "same"
242 | ? updatedData.map((row) =>
243 | row["archived"] == "true" ? "red" : "#00ADD8"
244 | )
245 | : updatedData.map((row) => mapLivenessToColor(row["liveness"])),
246 | },
247 | type: "scatter",
248 | };
249 |
250 | setData([trace]);
251 | };
252 |
253 | const loadData = async () => {
254 | if (dataRows.length == 0) {
255 | fetch(csvURL)
256 | .then((response) => response.text())
257 | .then((text) =>
258 | Papa.parse(text, { header: true, skipEmptyLines: true })
259 | )
260 | .then(function (result) {
261 | buildChartData(result.data);
262 | })
263 | .catch((e) => {
264 | console.error(`An error occurred: ${e}`);
265 | });
266 | } else {
267 | buildChartData(dataRows);
268 | }
269 | };
270 |
271 | useEffect(() => {
272 | loadData();
273 | }, [
274 | maxDaysLastCommit,
275 | minStars,
276 | minMentionableUsers,
277 | selectedAction,
278 | selectedXAxis,
279 | selectedYAxis,
280 | selectedSize,
281 | selectedBubbleColour,
282 | ]);
283 |
284 | const layout = {
285 | xaxis: {
286 | type: "log",
287 | title: selectedXAxis.label,
288 | gridcolor: "rgba(150, 150, 150, 0.6)",
289 | },
290 | yaxis: {
291 | type: "log",
292 | title: selectedYAxis.label,
293 | gridcolor: "rgba(150, 150, 150, 0.6)",
294 | },
295 | size: "stars",
296 | color: "main-category",
297 | hovermode: "closest",
298 | hover_name: "repo",
299 | showlegend: false,
300 | margin: {
301 | t: 30, // Adjusted top margin
302 | r: 20,
303 | b: 50, // Adjusted bottom margin
304 | l: 50,
305 | },
306 | paper_bgcolor: "rgb(0, 0, 0, 0.7)", // Transparent background
307 | plot_bgcolor: "rgba(38, 42, 51, 0.8)", // Dark background
308 | font: { color: "white" }, // White text
309 | };
310 |
311 | return (
312 |
320 |
328 |
handleInputChange(e, setMaxDaysLastCommit)}
335 | InputProps={{
336 | inputProps: {
337 | pattern: "[0-9]*",
338 | inputMode: "numeric",
339 | },
340 | }}
341 | />
342 | handleInputChange(e, setMinStars)}
349 | InputProps={{
350 | inputProps: {
351 | pattern: "[0-9]*",
352 | inputMode: "numeric",
353 | },
354 | }}
355 | />
356 | handleInputChange(e, setMinMentionableUsers)}
363 | InputProps={{
364 | inputProps: {
365 | pattern: "[0-9]*",
366 | inputMode: "numeric",
367 | },
368 | }}
369 | />
370 | (
378 |
384 | )}
385 | value={
386 | clickActions.find((element) => element.action === selectedAction) ??
387 | ""
388 | }
389 | onChange={(e, v, reason) => {
390 | if (reason === "clear") {
391 | setSelectedAction(clickActions[0].action);
392 | } else {
393 | setSelectedAction(v?.action);
394 | }
395 | }}
396 | />
397 | (
405 |
411 | )}
412 | value={
413 | axisMetrics.find(
414 | (element) => element.metric === selectedXAxis.metric
415 | ) ?? ""
416 | }
417 | onChange={(e, v, reason) => {
418 | if (reason === "clear") {
419 | setSelectedXAxis(axisMetrics[0]);
420 | } else {
421 | setSelectedXAxis(v);
422 | }
423 | }}
424 | />
425 | (
433 |
439 | )}
440 | value={
441 | axisMetrics.find(
442 | (element) => element.metric === selectedYAxis.metric
443 | ) ?? ""
444 | }
445 | onChange={(e, v, reason) => {
446 | if (reason === "clear") {
447 | setSelectedYAxis(axisMetrics[3]);
448 | } else {
449 | setSelectedYAxis(v);
450 | }
451 | }}
452 | />
453 | (
461 |
467 | )}
468 | value={
469 | sizeMetrics.find(
470 | (element) => element.metric === selectedSize.metric
471 | ) ?? ""
472 | }
473 | onChange={(e, v, reason) => {
474 | if (reason === "clear") {
475 | setSelectedSize(sizeMetrics[0]);
476 | } else {
477 | setSelectedSize(v);
478 | }
479 | }}
480 | />
481 | (
489 |
495 | )}
496 | value={
497 | bubbleColour.find(
498 | (element) => element.metric === selectedBubbleColour.metric
499 | ) ?? ""
500 | }
501 | onChange={(e, v, reason) => {
502 | if (reason === "clear") {
503 | setSelectedBubbleColour(bubbleColour[1]);
504 | } else {
505 | setSelectedBubbleColour(v);
506 | }
507 | }}
508 | />
509 |
510 |
handleBubbleClick(event)}
515 | />
516 |
517 | );
518 | };
519 |
520 | export default BubbleChart;
521 |
--------------------------------------------------------------------------------
/website/src/DepsChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from "react";
2 | import Linkweb from "@mui/material/Link";
3 | import Papa from "papaparse";
4 | import "./App.css";
5 | import { DataGrid, GridColDef } from "@mui/x-data-grid";
6 |
7 | const GitHubURL = "https://";
8 |
9 | const csvURL =
10 | "https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main/dep-repo-latest.csv";
11 |
12 | const columns: GridColDef[] = [
13 | {
14 | field: "dep",
15 | headerName: "Module",
16 | width: 220,
17 | renderCell: (params) => (
18 |
19 | {params.value}
20 |
21 | ),
22 | },
23 | {
24 | field: "awesome_go_repos_using_dep",
25 | headerName: "Repos #",
26 | width: 100,
27 | valueGetter: (val) => parseInt(val),
28 | },
29 | ];
30 |
31 | function DepsChart() {
32 | const [dataRows, setDataRows] = useState([]);
33 |
34 | const loadData = async () => {
35 | fetch(csvURL)
36 | .then((response) => response.text())
37 | .then((text) => Papa.parse(text, { header: true, skipEmptyLines: true }))
38 | .then(function (result) {
39 | console.log(result);
40 | setDataRows(result.data);
41 | })
42 | .catch((e) => {
43 | console.error(`An error occurred: ${e}`);
44 | });
45 | };
46 |
47 | useEffect(() => {
48 | loadData();
49 | }, []);
50 |
51 | return (
52 |
60 | row.dep}
62 | rows={dataRows}
63 | columns={columns}
64 | rowHeight={30}
65 | initialState={{
66 | pagination: {
67 | paginationModel: { page: 0, pageSize: 25 },
68 | },
69 | sorting: {
70 | sortModel: [{ field: "awesome_go_repos_using_dep", sort: "desc" }],
71 | },
72 | }}
73 | pageSizeOptions={[5, 10, 50]}
74 | />
75 |
76 | );
77 | }
78 |
79 | export default DepsChart;
80 |
--------------------------------------------------------------------------------
/website/src/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Linkweb from "@mui/material/Link";
3 | import GitHubButton from "react-github-btn";
4 |
5 | const awesomeGoUrl = "https://github.com/avelino/awesome-go";
6 |
7 | const csvURL =
8 | "https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main/analysis-latest.csv";
9 |
10 | function Header({ lastUpdate }) {
11 | const headerStyle = {
12 | display: "flex",
13 | alignItems: "center",
14 | backgroundColor: "#333",
15 | color: "#fff",
16 | padding: "10px",
17 | gap: "10px",
18 | height: "40px",
19 | };
20 |
21 | const githubButtonStyle = {
22 | marginTop: "5px",
23 | marginRight: "30px",
24 | };
25 |
26 | return (
27 |
28 |
29 | Awesome Go Stats
30 |
31 |
32 | CSV File
33 |
34 |
Last Update: {lastUpdate}
35 |
36 |
43 | Star Me
44 |
45 |
46 |
47 | );
48 | }
49 |
50 | export default Header;
51 |
--------------------------------------------------------------------------------
/website/src/TimeSeriesChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react";
2 | import FusionCharts from "fusioncharts";
3 | import TimeSeries from "fusioncharts/fusioncharts.timeseries";
4 | import ReactFC from "react-fusioncharts";
5 | import CandyTheme from "fusioncharts/themes/fusioncharts.theme.candy";
6 | import schemaStars from "./schema_stars";
7 | import schemaCommits from "./schema_commits";
8 |
9 | ReactFC.fcRoot(FusionCharts, TimeSeries, CandyTheme);
10 | const chart_props = {
11 | timeseriesDs: {
12 | type: "timeseries",
13 | width: "100%",
14 | height: "80%",
15 | dataEmptyMessage: "Fetching data...",
16 | dataSource: {
17 | caption: { text: "" },
18 | data: null,
19 | chart: {
20 | animation: "0",
21 | theme: "candy",
22 | exportEnabled: "1",
23 | exportMode: "client",
24 | exportFormats: "PNG=Export as PNG|PDF=Export as PDF",
25 | },
26 | },
27 | },
28 | };
29 |
30 | const API_BASE_URL =
31 | "https://raw.githubusercontent.com/emanuelef/awesome-go-repo-stats/main";
32 | const API_STARS_URL = `${API_BASE_URL}/stars-history-30d.json`;
33 | const API_COMMITS_URL = `${API_BASE_URL}/commits-history-30d.json`;
34 |
35 | function TimeSeriesChart({ repo, metric }) {
36 | const [ds, setds] = useState(chart_props);
37 | const [dataLoaded, setDataLoaded] = useState(false);
38 | const dataRef = useRef([]);
39 |
40 | const loadData = async () => {
41 | try {
42 | if (dataRef.current.length === 0) {
43 | console.log("load all data " + metric);
44 |
45 | const response = await fetch(
46 | metric == "Stars" ? API_STARS_URL : API_COMMITS_URL
47 | );
48 | const data = await response.json();
49 |
50 | console.log(data);
51 |
52 | dataRef.current = data;
53 | setDataLoaded(true);
54 | renderData();
55 | }
56 | } catch (err) {
57 | console.log(err);
58 | }
59 | };
60 |
61 | const renderData = () => {
62 | try {
63 | console.log(dataRef.current);
64 |
65 | if (dataRef.current.length === 0) {
66 | console.log("Rendering but no data");
67 | throw new Error("No data");
68 | }
69 |
70 | const dataRepo = dataRef.current[repo];
71 | const fusionTable = new FusionCharts.DataStore().createDataTable(
72 | dataRepo,
73 | metric == "Stars" ? schemaStars : schemaCommits
74 | );
75 | const options = { ...ds };
76 | options.timeseriesDs.dataSource.data = fusionTable;
77 | options.timeseriesDs.dataSource.caption = {
78 | text: `${repo}`,
79 | };
80 | options.timeseriesDs.dataSource.chart.exportFileName = `${repo.replace(
81 | "/",
82 | "_"
83 | )}-stars-history`;
84 | setds(options);
85 | } catch (err) {
86 | console.log(err);
87 | }
88 | };
89 |
90 | useEffect(() => {
91 | loadData();
92 | }, []);
93 |
94 | /*
95 | useEffect(() => {
96 | console.log("render");
97 | renderData();
98 | }, [repo]);
99 | */
100 |
101 | return (
102 |
109 | {dataLoaded && }
110 |
111 | );
112 | }
113 |
114 | export default TimeSeriesChart;
115 |
--------------------------------------------------------------------------------
/website/src/WaffleChart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import TextField from "@mui/material/TextField";
3 | import { ResponsiveWaffle } from "@nivo/waffle";
4 | import "./App.css";
5 |
6 | const initialDataSample = [
7 | {
8 | id: "satisified",
9 | label: "Ok",
10 | value: 0,
11 | },
12 | {
13 | id: "unsatisfied",
14 | label: "Non ok",
15 | value: 0,
16 | },
17 | ];
18 |
19 | function WaffleChart({ dataRows }) {
20 | const [minDaysLastCommit, setMinDaysLastCommit] = useState("30");
21 | const [minStars, setMinStars] = useState("10");
22 | const [minMentionableUsers, setMinMentionableUsers] = useState("10");
23 | const [data, setData] = useState(initialDataSample);
24 |
25 | const handleInputChange = (event, setStateFunction) => {
26 | const inputText = event.target.value;
27 |
28 | // Use a regular expression to check if the input contains only digits
29 | if (/^\d*$/.test(inputText)) {
30 | setStateFunction(inputText);
31 | }
32 | };
33 |
34 | const loadData = () => {
35 | const updatedDataSample = [
36 | {
37 | id: "unsatisfied",
38 | label: "Non ok",
39 | value: 0,
40 | },
41 | {
42 | id: "satisified",
43 | label: "Ok",
44 | value: 0,
45 | },
46 | ];
47 | dataRows.forEach((element) => {
48 | if (
49 | parseInt(element["days-last-commit"]) > parseInt(minDaysLastCommit) ||
50 | parseInt(element["stars"]) < parseInt(minStars) ||
51 | parseInt(element["mentionable-users"]) < parseInt(minMentionableUsers)
52 | ) {
53 | updatedDataSample[0].value++;
54 | } else {
55 | updatedDataSample[1].value++;
56 | }
57 | });
58 | console.log(updatedDataSample);
59 | setData(updatedDataSample);
60 | };
61 |
62 | useEffect(() => {
63 | loadData();
64 | }, [minDaysLastCommit, minStars, minMentionableUsers]);
65 |
66 | return (
67 |
68 | handleInputChange(e, setMinDaysLastCommit)}
74 | InputProps={{
75 | inputProps: {
76 | pattern: "[0-9]*",
77 | inputMode: "numeric",
78 | },
79 | }}
80 | />
81 | handleInputChange(e, setMinStars)}
87 | InputProps={{
88 | inputProps: {
89 | pattern: "[0-9]*",
90 | inputMode: "numeric",
91 | },
92 | }}
93 | />
94 | handleInputChange(e, setMinMentionableUsers)}
100 | InputProps={{
101 | inputProps: {
102 | pattern: "[0-9]*",
103 | inputMode: "numeric",
104 | },
105 | }}
106 | />
107 |
147 |
148 | );
149 | }
150 |
151 | export default WaffleChart;
152 |
--------------------------------------------------------------------------------
/website/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: "Figtree", -apple-system, BlinkMacSystemFont, "Segoe UI", "Figtree", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
4 | -webkit-font-smoothing: antialiased;
5 | -moz-osx-font-smoothing: grayscale;
6 | height: 100vh;
7 | background: #ffffff;
8 | font-size: 18px;
9 | }
10 |
11 | .app {
12 | background: #a8d5e5;
13 | }
14 |
15 | h1 {
16 | color: #165a72;
17 | margin: 50px auto;
18 | font-size: 40px;
19 | }
20 |
21 | .menu1 {
22 | margin-bottom: 40px;
23 | margin-top: 20px;
24 | }
25 |
26 | h2 {
27 | color: #165a72;
28 | }
--------------------------------------------------------------------------------
/website/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./index.css";
4 | import App from "./App";
5 | const root = ReactDOM.createRoot(document.getElementById("root"));
6 | import { HashRouter } from "react-router-dom";
7 | import GitHubCorners from "@uiw/react-github-corners";
8 | root.render(
9 |
10 |
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/website/src/schema_commits.js:
--------------------------------------------------------------------------------
1 | const schema = [
2 | {
3 | name: "Time",
4 | type: "date",
5 | format: "%-d-%-m-%Y",
6 | },
7 | {
8 | name: "New Commits",
9 | type: "number",
10 | },
11 | {
12 | name: "Total Commits",
13 | type: "number",
14 | },
15 | ];
16 |
17 | export default schema;
18 |
--------------------------------------------------------------------------------
/website/src/schema_stars.js:
--------------------------------------------------------------------------------
1 | const schema = [
2 | {
3 | name: "Time",
4 | type: "date",
5 | format: "%-d-%-m-%Y",
6 | },
7 | {
8 | name: "New Stars",
9 | type: "number",
10 | },
11 | {
12 | name: "Total Stars",
13 | type: "number",
14 | },
15 | ];
16 |
17 | export default schema;
18 |
--------------------------------------------------------------------------------
/website/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 |
9 | /* Bundler mode */
10 | "moduleResolution": "bundler",
11 | "allowImportingTsExtensions": true,
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "noEmit": true,
15 | "jsx": "react-jsx",
16 |
17 | /* Linting */
18 | "strict": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": ["src"],
24 | "references": [{ "path": "./tsconfig.node.json" }]
25 | }
26 |
--------------------------------------------------------------------------------
/website/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "skipLibCheck": true,
5 | "module": "ESNext",
6 | "moduleResolution": "bundler",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "include": ["vite.config.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/website/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | base: '/awesome-go-repo-stats/',
7 | plugins: [react()],
8 | })
9 |
--------------------------------------------------------------------------------