├── .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 | Screenshot 2023-11-04 at 22 31 59 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 | Screenshot 2023-11-04 at 22 33 09 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 | { 587 | if (level >= 0) 588 | return { 589 | color: disabled ? "#f5d9ff" : "#07100d", 590 | backgroundColor: active ? "#00cef9" : "undefined", 591 | }; 592 | }, 593 | }} 594 | > 595 | } 597 | className="menu1" 598 | icon={ 599 | { 601 | setCollapsed(!collapsed); 602 | }} 603 | /> 604 | } 605 | > 606 |

Awesome Go Stats

607 |
608 | } 610 | icon={} 611 | > 612 | Table 613 | 614 | } 616 | icon={} 617 | > 618 | Dependencies 619 | 620 | } 622 | icon={} 623 | > 624 | Bubble 625 | 626 | 632 | } 633 | icon={} 634 | > 635 | StarsTimeline 636 | 637 |
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 |
11 | 12 | 17 | 18 | 19 |
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 | --------------------------------------------------------------------------------