stackvitals 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mergescout/__init__.py +1 -0
- mergescout/archive/__init__.py +0 -0
- mergescout/archive/__main__.py +30 -0
- mergescout/archive/convert.py +83 -0
- mergescout/archive/events.py +55 -0
- mergescout/archive/manifest.py +45 -0
- mergescout/archive/stream.py +83 -0
- mergescout/bench/__init__.py +1 -0
- mergescout/bench/__main__.py +135 -0
- mergescout/bench/ci.py +257 -0
- mergescout/bench/deploys.py +115 -0
- mergescout/bench/matched.py +22 -0
- mergescout/bench/prior.py +42 -0
- mergescout/bench/report.py +230 -0
- mergescout/bench/shared.py +112 -0
- mergescout/bench/stars.py +57 -0
- mergescout/cli.py +89 -0
- mergescout/config.py +35 -0
- mergescout/crawl/__init__.py +0 -0
- mergescout/crawl/__main__.py +97 -0
- mergescout/crawl/chain.py +34 -0
- mergescout/crawl/fetch.py +146 -0
- mergescout/crawl/outcomes.py +69 -0
- mergescout/crawl/queries.py +250 -0
- mergescout/crawl/runner.py +176 -0
- mergescout/crawl/seed.py +77 -0
- mergescout/crawl/state.py +169 -0
- mergescout/crawl/writer.py +87 -0
- mergescout/discover/__init__.py +0 -0
- mergescout/discover/__main__.py +58 -0
- mergescout/discover/sweep.py +286 -0
- mergescout/github/__init__.py +0 -0
- mergescout/github/graphql.py +202 -0
- mergescout/github/identities.py +35 -0
- mergescout/github/releases.py +137 -0
- mergescout/labels.py +20 -0
- mergescout/mcp_server.py +228 -0
- mergescout/measure/__init__.py +0 -0
- mergescout/measure/__main__.py +75 -0
- mergescout/measure/api_cost.py +91 -0
- mergescout/measure/count_repos.py +30 -0
- mergescout/measure/jsonl.py +14 -0
- mergescout/measure/report.py +56 -0
- mergescout/measure/sample.py +37 -0
- mergescout/owners.py +22 -0
- mergescout/setup_check.py +64 -0
- mergescout/signals/__init__.py +0 -0
- mergescout/signals/__main__.py +96 -0
- mergescout/signals/bump_ledger.py +258 -0
- mergescout/signals/bumps.py +104 -0
- mergescout/signals/check.py +141 -0
- mergescout/signals/community_bench.py +149 -0
- mergescout/signals/delivery_bench.py +103 -0
- mergescout/signals/deps_map.py +261 -0
- mergescout/signals/deps_trends.py +117 -0
- mergescout/signals/evidence.py +94 -0
- mergescout/signals/exploits.py +43 -0
- mergescout/signals/flow_bench.py +258 -0
- mergescout/signals/identity.py +73 -0
- mergescout/signals/issues.py +76 -0
- mergescout/signals/labels.py +79 -0
- mergescout/signals/osv.py +232 -0
- mergescout/signals/owners_watch.py +80 -0
- mergescout/signals/repo_stats.py +96 -0
- mergescout/signals/rules.py +70 -0
- mergescout/signals/silver.py +33 -0
- mergescout/signals/top_packages.py +26 -0
- mergescout/signals/triage.py +71 -0
- mergescout/signals/triage_math.py +120 -0
- mergescout/signals/walls.py +91 -0
- mergescout/snapshot/__init__.py +0 -0
- mergescout/snapshot/__main__.py +215 -0
- mergescout/snapshot/build.py +147 -0
- mergescout/snapshot/publish.py +70 -0
- mergescout/snapshot/schema.py +34 -0
- mergescout/snapshot/score.py +30 -0
- mergescout/store/__init__.py +0 -0
- mergescout/store/__main__.py +46 -0
- mergescout/store/sync.py +148 -0
- mergescout/timeutil.py +11 -0
- mergescout/tool/__init__.py +0 -0
- mergescout/tool/action_pack.py +62 -0
- mergescout/tool/live.py +201 -0
- mergescout/tool/queries.py +293 -0
- mergescout/tool/refs.py +31 -0
- mergescout/tool/store.py +129 -0
- mergescout/tool/tracking.py +83 -0
- stackvitals/__init__.py +2 -0
- stackvitals/__main__.py +4 -0
- stackvitals/ci/__init__.py +1 -0
- stackvitals/ci/action.py +574 -0
- stackvitals/ci/analyze.py +577 -0
- stackvitals/ci/api.py +218 -0
- stackvitals/ci/automation.py +212 -0
- stackvitals/ci/bench.py +195 -0
- stackvitals/ci/burnin.py +102 -0
- stackvitals/ci/cli.py +570 -0
- stackvitals/ci/collect.py +239 -0
- stackvitals/ci/cost.py +107 -0
- stackvitals/ci/coverage.py +155 -0
- stackvitals/ci/feedback.py +699 -0
- stackvitals/ci/fingerprint.py +220 -0
- stackvitals/ci/gitlab.py +121 -0
- stackvitals/ci/gitlab_action.py +125 -0
- stackvitals/ci/impact.py +125 -0
- stackvitals/ci/investigate.py +59 -0
- stackvitals/ci/kinds.py +52 -0
- stackvitals/ci/monitors.py +105 -0
- stackvitals/ci/owners.py +116 -0
- stackvitals/ci/quarantine_runtime.py +90 -0
- stackvitals/ci/report.py +435 -0
- stackvitals/ci/retry.py +67 -0
- stackvitals/ci/selfheal.py +102 -0
- stackvitals/ci/shards.py +120 -0
- stackvitals/ci/share.py +59 -0
- stackvitals/ci/store.py +93 -0
- stackvitals/ci/testreports.py +351 -0
- stackvitals/ci/tickets.py +106 -0
- stackvitals/ci/track_record.json +11 -0
- stackvitals/ci/triage.py +130 -0
- stackvitals/ci/triggers.py +147 -0
- stackvitals/ci/upload.py +85 -0
- stackvitals/ci/validate.py +91 -0
- stackvitals/cli.py +111 -0
- stackvitals/codeowners.py +69 -0
- stackvitals/community/__init__.py +0 -0
- stackvitals/community/action.py +140 -0
- stackvitals/community/affiliation.py +51 -0
- stackvitals/community/analyze.py +507 -0
- stackvitals/community/build.py +56 -0
- stackvitals/community/chaoss.py +70 -0
- stackvitals/community/cli.py +79 -0
- stackvitals/community/collect.py +240 -0
- stackvitals/community/context.py +54 -0
- stackvitals/community/report.py +250 -0
- stackvitals/connectors/__init__.py +244 -0
- stackvitals/connectors/ai_spend.py +190 -0
- stackvitals/connectors/base.py +82 -0
- stackvitals/connectors/check.py +686 -0
- stackvitals/connectors/copilot.py +104 -0
- stackvitals/connectors/discord.py +103 -0
- stackvitals/connectors/discourse.py +117 -0
- stackvitals/connectors/incident_io.py +106 -0
- stackvitals/connectors/jira.py +210 -0
- stackvitals/connectors/linear.py +140 -0
- stackvitals/connectors/pagerduty.py +96 -0
- stackvitals/connectors/slack.py +110 -0
- stackvitals/connectors/sonar.py +77 -0
- stackvitals/connectors/store.py +120 -0
- stackvitals/connectors/tracker.py +108 -0
- stackvitals/connectors/writeback.py +105 -0
- stackvitals/contribute/__init__.py +1 -0
- stackvitals/contribute/brief.py +297 -0
- stackvitals/contribute/cli.py +64 -0
- stackvitals/core/__init__.py +1 -0
- stackvitals/core/actors.py +103 -0
- stackvitals/core/agent_ledger.py +120 -0
- stackvitals/core/agreements.py +95 -0
- stackvitals/core/ai_outcomes.py +276 -0
- stackvitals/core/alerts.py +224 -0
- stackvitals/core/appdata.py +112 -0
- stackvitals/core/bucket.py +190 -0
- stackvitals/core/bugs.py +43 -0
- stackvitals/core/calendar.py +34 -0
- stackvitals/core/capex.py +57 -0
- stackvitals/core/change_risk.py +181 -0
- stackvitals/core/changes.py +133 -0
- stackvitals/core/chat.py +143 -0
- stackvitals/core/config.py +373 -0
- stackvitals/core/dataflow.py +168 -0
- stackvitals/core/decisions.py +275 -0
- stackvitals/core/delivery.py +954 -0
- stackvitals/core/delivery_peers.py +98 -0
- stackvitals/core/delivery_report.py +429 -0
- stackvitals/core/deliverycli.py +147 -0
- stackvitals/core/devlake_dora.sql +485 -0
- stackvitals/core/digest.py +333 -0
- stackvitals/core/explaincli.py +107 -0
- stackvitals/core/explore.py +296 -0
- stackvitals/core/export.py +342 -0
- stackvitals/core/exportcli.py +117 -0
- stackvitals/core/findings.py +350 -0
- stackvitals/core/health.py +94 -0
- stackvitals/core/identity.py +82 -0
- stackvitals/core/insights.py +365 -0
- stackvitals/core/insightscli.py +43 -0
- stackvitals/core/installdoc.py +69 -0
- stackvitals/core/investment.py +187 -0
- stackvitals/core/joins.py +43 -0
- stackvitals/core/llmsdoc.py +40 -0
- stackvitals/core/merge_queue.py +70 -0
- stackvitals/core/metrics.py +109 -0
- stackvitals/core/model.py +285 -0
- stackvitals/core/projectcli.py +66 -0
- stackvitals/core/propose.py +86 -0
- stackvitals/core/pulse.py +85 -0
- stackvitals/core/recorded.py +145 -0
- stackvitals/core/releases.py +39 -0
- stackvitals/core/requests.py +528 -0
- stackvitals/core/resync.py +35 -0
- stackvitals/core/schema.py +205 -0
- stackvitals/core/services.py +203 -0
- stackvitals/core/settings.py +96 -0
- stackvitals/core/sqlcli.py +111 -0
- stackvitals/core/stats.py +222 -0
- stackvitals/core/store.py +125 -0
- stackvitals/core/summary.py +104 -0
- stackvitals/core/time.py +29 -0
- stackvitals/core/tomledit.py +149 -0
- stackvitals/core/uicli.py +138 -0
- stackvitals/core/webapp.py +79 -0
- stackvitals/core/what_changed.py +76 -0
- stackvitals/core/workspace.py +194 -0
- stackvitals/deps/__init__.py +1 -0
- stackvitals/deps/action.py +299 -0
- stackvitals/deps/actions_vulns.py +89 -0
- stackvitals/deps/cli.py +77 -0
- stackvitals/deps/dossier.py +341 -0
- stackvitals/deps/evidence.py +44 -0
- stackvitals/deps/fixplan.py +40 -0
- stackvitals/deps/guarddog.py +32 -0
- stackvitals/deps/hygiene.py +47 -0
- stackvitals/deps/imports.py +113 -0
- stackvitals/deps/lockfiles.py +198 -0
- stackvitals/deps/lookalike.py +80 -0
- stackvitals/deps/reach.py +59 -0
- stackvitals/deps/release.py +110 -0
- stackvitals/deps/runtimes.py +161 -0
- stackvitals/deps/supply.py +454 -0
- stackvitals/deps/vulns.py +588 -0
- stackvitals/editors/__init__.py +40 -0
- stackvitals/editors/vscode/extension.js +101 -0
- stackvitals/editors/vscode/marks.js +92 -0
- stackvitals/editors/vscode/package.json +26 -0
- stackvitals/flow/__init__.py +1 -0
- stackvitals/flow/action.py +178 -0
- stackvitals/flow/analyze.py +418 -0
- stackvitals/flow/automate.py +72 -0
- stackvitals/flow/build.py +46 -0
- stackvitals/flow/cli.py +93 -0
- stackvitals/flow/code_hosts.py +146 -0
- stackvitals/flow/collect.py +451 -0
- stackvitals/flow/gitlab_mr.py +75 -0
- stackvitals/flow/knowledge.py +202 -0
- stackvitals/flow/peers.py +90 -0
- stackvitals/flow/preflight.py +210 -0
- stackvitals/flow/report.py +253 -0
- stackvitals/flow/rules.py +135 -0
- stackvitals/flow/store.py +108 -0
- stackvitals/flow/survival.py +84 -0
- stackvitals/flow/triage.py +62 -0
- stackvitals/mcp_server.py +670 -0
- stackvitals/portal/__init__.py +0 -0
- stackvitals/portal/action.py +205 -0
- stackvitals/portal/batch.py +80 -0
- stackvitals/portal/campaigns.py +104 -0
- stackvitals/portal/catalog.py +104 -0
- stackvitals/portal/checks.py +63 -0
- stackvitals/portal/cli.py +172 -0
- stackvitals/portal/descriptor.py +105 -0
- stackvitals/portal/export.py +43 -0
- stackvitals/portal/github.py +48 -0
- stackvitals/portal/golden.py +95 -0
- stackvitals/portal/history.py +30 -0
- stackvitals/portal/inventory.py +264 -0
- stackvitals/portal/owners.py +78 -0
- stackvitals/portal/pages.py +153 -0
- stackvitals/portal/rollup.py +32 -0
- stackvitals/portal/scorecard.py +366 -0
- stackvitals/security/__init__.py +2 -0
- stackvitals/security/probes.py +154 -0
- stackvitals/security/repo_checks.py +132 -0
- stackvitals/security/workflows.py +111 -0
- stackvitals/shell.py +107 -0
- stackvitals/site.py +51 -0
- stackvitals/web/app/LICENSES.md +428 -0
- stackvitals/web/app/boot.js +1 -0
- stackvitals/web/app/chunks/AddRepo-KRPZXWCY.js +1 -0
- stackvitals/web/app/chunks/SetupRunning-XWFQAKRZ.js +1 -0
- stackvitals/web/app/chunks/accuracy-data-OQTDXI7Y.js +1 -0
- stackvitals/web/app/chunks/agents-data-BAL6QAOF.js +11 -0
- stackvitals/web/app/chunks/chunk-24LFKFZ6.js +1 -0
- stackvitals/web/app/chunks/chunk-3G4SQNUC.js +1 -0
- stackvitals/web/app/chunks/chunk-3IXWEOIJ.js +154 -0
- stackvitals/web/app/chunks/chunk-3ROQFHPV.js +1 -0
- stackvitals/web/app/chunks/chunk-45PGAQRO.js +8 -0
- stackvitals/web/app/chunks/chunk-4H53OYLG.js +1 -0
- stackvitals/web/app/chunks/chunk-4LUI7O5P.js +1 -0
- stackvitals/web/app/chunks/chunk-4SQPQ5TB.js +1 -0
- stackvitals/web/app/chunks/chunk-4WKQTWUC.js +2 -0
- stackvitals/web/app/chunks/chunk-576YHDEK.js +8 -0
- stackvitals/web/app/chunks/chunk-6BDBXA6V.js +1 -0
- stackvitals/web/app/chunks/chunk-6RLVD2DT.js +1 -0
- stackvitals/web/app/chunks/chunk-763NH7PP.js +1 -0
- stackvitals/web/app/chunks/chunk-76WAVKUG.js +1 -0
- stackvitals/web/app/chunks/chunk-7II2BD5F.js +1 -0
- stackvitals/web/app/chunks/chunk-7TJCBOAY.js +1 -0
- stackvitals/web/app/chunks/chunk-ASYNLVOP.js +1 -0
- stackvitals/web/app/chunks/chunk-CTKDYZMG.js +1 -0
- stackvitals/web/app/chunks/chunk-CVB26NMC.js +1 -0
- stackvitals/web/app/chunks/chunk-G7NGB5UX.js +1 -0
- stackvitals/web/app/chunks/chunk-GPVLJRZD.js +1 -0
- stackvitals/web/app/chunks/chunk-GQYFN4PE.js +75 -0
- stackvitals/web/app/chunks/chunk-GT4EAKAO.js +2 -0
- stackvitals/web/app/chunks/chunk-IYMBFPCV.js +1 -0
- stackvitals/web/app/chunks/chunk-LAAGCPU3.js +1 -0
- stackvitals/web/app/chunks/chunk-MAPYGK3P.js +1 -0
- stackvitals/web/app/chunks/chunk-P5TXDD45.js +1 -0
- stackvitals/web/app/chunks/chunk-PABICGBL.js +1 -0
- stackvitals/web/app/chunks/chunk-RPXHAG6F.js +1 -0
- stackvitals/web/app/chunks/chunk-RQZPSJ2Y.js +1 -0
- stackvitals/web/app/chunks/chunk-S2ZBMEVA.js +1 -0
- stackvitals/web/app/chunks/chunk-S654BBEP.js +6 -0
- stackvitals/web/app/chunks/chunk-T7QVTNXF.js +1 -0
- stackvitals/web/app/chunks/chunk-THFUOCPC.js +273 -0
- stackvitals/web/app/chunks/chunk-TO4VVRZ7.js +1 -0
- stackvitals/web/app/chunks/chunk-VQ5YXO54.js +1 -0
- stackvitals/web/app/chunks/chunk-VWKSMFWJ.js +1 -0
- stackvitals/web/app/chunks/chunk-Y6LUXDM6.js +1 -0
- stackvitals/web/app/chunks/chunk-YTANGYYH.js +1 -0
- stackvitals/web/app/chunks/connect-KONYWYTT.js +1 -0
- stackvitals/web/app/chunks/connections-data-2DYS4UVW.js +32 -0
- stackvitals/web/app/chunks/logos-N5IINUAM.js +1 -0
- stackvitals/web/app/chunks/more-KMJMJQD3.js +10 -0
- stackvitals/web/app/chunks/more-PC6FVS3P.js +1 -0
- stackvitals/web/app/chunks/more-RCIVCUDE.js +2 -0
- stackvitals/web/app/chunks/more-ZKKRY6NO.css +1 -0
- stackvitals/web/app/chunks/sealbox-35YL4EUH.js +3 -0
- stackvitals/web/app/chunks/session-DYYTOE2K.js +1 -0
- stackvitals/web/app/chunks/settingsApply-2HRJEZYM.js +1 -0
- stackvitals/web/app/chunks/settingsReview-GX2FZQ4M.js +3 -0
- stackvitals/web/app/chunks/uPlot.esm-OIWT3AHJ.js +3 -0
- stackvitals/web/app/demo/ci/impact.json +1 -0
- stackvitals/web/app/demo/ci/quarantine.json +1 -0
- stackvitals/web/app/demo/ci/tickets.json +1 -0
- stackvitals/web/app/demo/mobile/alerts.json +1 -0
- stackvitals/web/app/demo/mobile/app.json +1 -0
- stackvitals/web/app/demo/mobile/community.json +1 -0
- stackvitals/web/app/demo/mobile/connectors.json +1 -0
- stackvitals/web/app/demo/mobile/data.json +1 -0
- stackvitals/web/app/demo/mobile/decisions.json +1 -0
- stackvitals/web/app/demo/mobile/delivery.json +1 -0
- stackvitals/web/app/demo/mobile/deps.json +1 -0
- stackvitals/web/app/demo/mobile/flow.json +1 -0
- stackvitals/web/app/demo/mobile/health.json +1 -0
- stackvitals/web/app/demo/mobile/insights.json +1 -0
- stackvitals/web/app/demo/mobile/pulse.json +1 -0
- stackvitals/web/app/demo/mobile/security.json +1 -0
- stackvitals/web/app/demo/mobile/summary/ci_jobs.jsonl +5 -0
- stackvitals/web/app/demo/mobile/summary/devlake_project_pr_metrics.jsonl +14 -0
- stackvitals/web/app/demo/mobile/summary/devlake_pull_requests.jsonl +14 -0
- stackvitals/web/app/demo/mobile/summary/manifest.json +1 -0
- stackvitals/web/app/demo/mobile/summary/prs.jsonl +19 -0
- stackvitals/web/app/demo/mobile/summary/tests.jsonl +1 -0
- stackvitals/web/app/demo/mobile/supply.json +1 -0
- stackvitals/web/app/demo/mobile/vulns.json +1 -0
- stackvitals/web/app/demo/payments-api/alerts.json +1 -0
- stackvitals/web/app/demo/payments-api/app.json +1 -0
- stackvitals/web/app/demo/payments-api/community.json +1 -0
- stackvitals/web/app/demo/payments-api/connectors.json +1 -0
- stackvitals/web/app/demo/payments-api/data.json +1 -0
- stackvitals/web/app/demo/payments-api/decisions.json +1 -0
- stackvitals/web/app/demo/payments-api/delivery.json +1 -0
- stackvitals/web/app/demo/payments-api/deps.json +1 -0
- stackvitals/web/app/demo/payments-api/flow.json +1 -0
- stackvitals/web/app/demo/payments-api/health.json +1 -0
- stackvitals/web/app/demo/payments-api/insights.json +1 -0
- stackvitals/web/app/demo/payments-api/pulse.json +1 -0
- stackvitals/web/app/demo/payments-api/security.json +1 -0
- stackvitals/web/app/demo/payments-api/summary/ci_jobs.jsonl +5 -0
- stackvitals/web/app/demo/payments-api/summary/devlake_project_pr_metrics.jsonl +14 -0
- stackvitals/web/app/demo/payments-api/summary/devlake_pull_requests.jsonl +14 -0
- stackvitals/web/app/demo/payments-api/summary/manifest.json +1 -0
- stackvitals/web/app/demo/payments-api/summary/prs.jsonl +22 -0
- stackvitals/web/app/demo/payments-api/summary/tests.jsonl +3 -0
- stackvitals/web/app/demo/payments-api/supply.json +1 -0
- stackvitals/web/app/demo/payments-api/vulns.json +1 -0
- stackvitals/web/app/demo/portal.json +1 -0
- stackvitals/web/app/demo/settings.json +1 -0
- stackvitals/web/app/demo/web-app/alerts.json +1 -0
- stackvitals/web/app/demo/web-app/app.json +1 -0
- stackvitals/web/app/demo/web-app/community.json +1 -0
- stackvitals/web/app/demo/web-app/connectors.json +1 -0
- stackvitals/web/app/demo/web-app/data.json +1 -0
- stackvitals/web/app/demo/web-app/decisions.json +1 -0
- stackvitals/web/app/demo/web-app/delivery.json +1 -0
- stackvitals/web/app/demo/web-app/deps.json +1 -0
- stackvitals/web/app/demo/web-app/flow.json +1 -0
- stackvitals/web/app/demo/web-app/health.json +1 -0
- stackvitals/web/app/demo/web-app/insights.json +1 -0
- stackvitals/web/app/demo/web-app/pulse.json +1 -0
- stackvitals/web/app/demo/web-app/security.json +1 -0
- stackvitals/web/app/demo/web-app/summary/ci_jobs.jsonl +5 -0
- stackvitals/web/app/demo/web-app/summary/devlake_project_pr_metrics.jsonl +14 -0
- stackvitals/web/app/demo/web-app/summary/devlake_pull_requests.jsonl +14 -0
- stackvitals/web/app/demo/web-app/summary/manifest.json +1 -0
- stackvitals/web/app/demo/web-app/summary/prs.jsonl +18 -0
- stackvitals/web/app/demo/web-app/summary/tests.jsonl +2 -0
- stackvitals/web/app/demo/web-app/supply.json +1 -0
- stackvitals/web/app/demo/web-app/vulns.json +1 -0
- stackvitals/web/app/fonts/plex-mono-400.woff2 +0 -0
- stackvitals/web/app/fonts/plex-sans-400.woff2 +0 -0
- stackvitals/web/app/fonts/plex-sans-600.woff2 +0 -0
- stackvitals/web/app/fonts/plex-sans-condensed-700.woff2 +0 -0
- stackvitals/web/app/help/accuracy.json +1 -0
- stackvitals/web/app/help/connectors.json +1 -0
- stackvitals/web/app/help/getting-started.json +1 -0
- stackvitals/web/app/help/index.json +1 -0
- stackvitals/web/app/help/modules.json +1 -0
- stackvitals/web/app/help/privacy-and-security.json +1 -0
- stackvitals/web/app/help/reference.json +1 -0
- stackvitals/web/app/main.css +1 -0
- stackvitals/web/app/main.js +1 -0
- stackvitals/web/app/pages/about.css +1 -0
- stackvitals/web/app/pages/about.js +1 -0
- stackvitals/web/app/pages/activity.css +1 -0
- stackvitals/web/app/pages/activity.js +1 -0
- stackvitals/web/app/pages/agents.css +1 -0
- stackvitals/web/app/pages/agents.js +2 -0
- stackvitals/web/app/pages/ci.css +1 -0
- stackvitals/web/app/pages/ci.js +1 -0
- stackvitals/web/app/pages/community.css +1 -0
- stackvitals/web/app/pages/community.js +1 -0
- stackvitals/web/app/pages/connections.css +1 -0
- stackvitals/web/app/pages/connections.js +6 -0
- stackvitals/web/app/pages/delivery.css +1 -0
- stackvitals/web/app/pages/delivery.js +45 -0
- stackvitals/web/app/pages/deps.css +1 -0
- stackvitals/web/app/pages/deps.js +7 -0
- stackvitals/web/app/pages/explore.css +1 -0
- stackvitals/web/app/pages/explore.js +7 -0
- stackvitals/web/app/pages/health.css +1 -0
- stackvitals/web/app/pages/health.js +1 -0
- stackvitals/web/app/pages/help.css +1 -0
- stackvitals/web/app/pages/help.js +6 -0
- stackvitals/web/app/pages/insights.css +1 -0
- stackvitals/web/app/pages/insights.js +1 -0
- stackvitals/web/app/pages/investment.css +1 -0
- stackvitals/web/app/pages/investment.js +3 -0
- stackvitals/web/app/pages/overview.css +1 -0
- stackvitals/web/app/pages/overview.js +1 -0
- stackvitals/web/app/pages/portal.css +1 -0
- stackvitals/web/app/pages/portal.js +51 -0
- stackvitals/web/app/pages/prs.css +1 -0
- stackvitals/web/app/pages/prs.js +4 -0
- stackvitals/web/app/pages/settings.css +1 -0
- stackvitals/web/app/pages/settings.js +1 -0
- stackvitals/web/app/pages/setup.css +1 -0
- stackvitals/web/app/pages/setup.js +2 -0
- stackvitals/web/app/pages/standing.css +1 -0
- stackvitals/web/app/pages/standing.js +1 -0
- stackvitals/web/app/pages/welcome-sheet.js +1 -0
- stackvitals/web/app/pages/welcome.js +1 -0
- stackvitals/web/app/settings-workflow.json +1 -0
- stackvitals/web/index.html +34 -0
- stackvitals/web/site/index.html +108 -0
- stackvitals/web/site/integrations.html +15 -0
- stackvitals/web/site/setup.html +15 -0
- stackvitals/web/site/site.css +68 -0
- stackvitals-0.1.0.dist-info/METADATA +134 -0
- stackvitals-0.1.0.dist-info/RECORD +465 -0
- stackvitals-0.1.0.dist-info/WHEEL +4 -0
- stackvitals-0.1.0.dist-info/entry_points.txt +3 -0
- stackvitals-0.1.0.dist-info/licenses/LICENSE +202 -0
- stackvitals-0.1.0.dist-info/licenses/NOTICE +16 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""explore.html: SQL in the browser over the data branch's summaries, with DuckDB-WASM (pinned, integrity-checked) and
|
|
2
|
+
saved queries that are DevLake's own dashboard SQL on DevLake-shaped tables. On Pages it reads the files from GitHub's
|
|
3
|
+
raw host (Pages serves only docs/); `stackvitals ui` serves it next to the data, which it then reads directly."""
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from stackvitals.core.export import COLUMNS, _sql_text
|
|
8
|
+
from stackvitals.core.summary import TYPES
|
|
9
|
+
|
|
10
|
+
DUCKDB = "1.32.0"
|
|
11
|
+
ARROW = "17.0.0"
|
|
12
|
+
# jsDelivr's SHA-256 of each file of the pinned release; a changed file is refused by the browser.
|
|
13
|
+
SRI = {"duckdb-browser.mjs": "sha256-T4wLs8I9HsRXpFHMJ0GFl7mkDddyzOrMQ2ost7mknBg=",
|
|
14
|
+
"duckdb-browser-eh.worker.js": "sha256-+KtytrkLOtgwd9R0JtSpnV2aTH4Hy6GivjfWVa3Hwas=",
|
|
15
|
+
"duckdb-eh.wasm": "sha256-TCIb+lnBHyTb11DnDJC5JS7KbuxWM5Nuai7HZuVf2Hk=",
|
|
16
|
+
"duckdb-browser-mvp.worker.js": "sha256-sDhwJ/F04rYMLVz6Mc7MqbidipdiNGtkSaeEzRxN3jw=",
|
|
17
|
+
"duckdb-mvp.wasm": "sha256-Rdcqgfuo5XaT2JDag3xwQTEOOF51YZqFWYObFTiN/pc="}
|
|
18
|
+
DORA_TITLES = {"deployment_frequency": "DORA: deployment frequency", "lead_time": "DORA: median lead time for changes (hours)",
|
|
19
|
+
"change_failure_rate": "DORA: change failure rate", "recovery": "DORA: failed deployment recovery time (hours)",
|
|
20
|
+
"monthly": "DORA: monthly deployments", "pr_cycle_time": "PR cycle time by month (hours)"}
|
|
21
|
+
# More of DevLake's panels, ported like the DORA ones (GitHub, Engineering Overview and Contributor Experience dashboards);
|
|
22
|
+
# Apache DevLake's SQL, Apache License 2.0 (see NOTICE).
|
|
23
|
+
DEVLAKE_MORE = [
|
|
24
|
+
("GitHub: share of closed PRs that were never merged",
|
|
25
|
+
"select count(distinct case when status = 'CLOSED' then id else null end) / count(distinct case when status in "
|
|
26
|
+
"('CLOSED', 'MERGED') then id else null end) as ratio\nfrom pull_requests pr\nwhere created_date between "
|
|
27
|
+
"TIMESTAMP '{{FROM}}' and TIMESTAMP '{{TO}}'"),
|
|
28
|
+
("GitHub: mean time to merge by month (days)",
|
|
29
|
+
"with _prs as (\n select date_trunc('month', created_date) as time, avg((epoch(merged_date) - epoch(created_date)) / 86400) "
|
|
30
|
+
"as time_to_merge\n from pull_requests\n where created_date between TIMESTAMP '{{FROM}}' and TIMESTAMP '{{TO}}'\n group by 1)\n"
|
|
31
|
+
"select strftime(time, '%B %Y') as _time, time_to_merge as \"Time to Merge\" from _prs order by time"),
|
|
32
|
+
("Engineering Overview: PRs merged by month",
|
|
33
|
+
"with _merged_prs as (\n select date_trunc('month', pr.merged_date) as time, count(distinct pr.id) as pr_merged_count\n"
|
|
34
|
+
" from pull_requests pr join project_mapping pm on pr.base_repo_id = pm.row_id and pm.\"table\" = 'repos'\n"
|
|
35
|
+
" where pm.project_name in ('{{PROJECT}}') and pr.merged_date is not null\n"
|
|
36
|
+
" and pr.merged_date between TIMESTAMP '{{FROM}}' and TIMESTAMP '{{TO}}'\n"
|
|
37
|
+
" and pr.merged_date >= date_trunc('month', TIMESTAMP '{{FROM}}') + INTERVAL 1 MONTH\n group by time)\n"
|
|
38
|
+
"select strftime(time, '%B %Y') as month, pr_merged_count from _merged_prs order by time"),
|
|
39
|
+
("Contributor Experience: PRs closed without merging, last month (%)",
|
|
40
|
+
"select 100 * count(distinct case when status = 'CLOSED' then id else null end) / count(distinct case when status in "
|
|
41
|
+
"('CLOSED', 'MERGED') then id else null end) as ratio\nfrom pull_requests pr\nwhere cast(created_date as date) between "
|
|
42
|
+
"date_trunc('month', current_date) - INTERVAL 1 MONTH and date_trunc('month', current_date) - INTERVAL 1 DAY"),
|
|
43
|
+
]
|
|
44
|
+
OURS = [
|
|
45
|
+
("Pull requests merged per week, with median pickup (people)",
|
|
46
|
+
"select date_trunc('week', cast(merged_at as timestamp)) as week, count(*) as merged,\n"
|
|
47
|
+
" round(median(pickup_s) / 3600, 1) as median_pickup_hours\nfrom prs where merged_at is not null and author_kind = 'person'\n"
|
|
48
|
+
"group by 1 order by 1"),
|
|
49
|
+
("Insiders and outside contributors",
|
|
50
|
+
"select role, count(*) as prs, count(merged_at) as merged, round(median(pickup_s) / 3600, 1) as median_pickup_hours\n"
|
|
51
|
+
"from prs where author_kind = 'person' group by 1 order by 2 desc"),
|
|
52
|
+
("Flakiest CI jobs",
|
|
53
|
+
"select workflow, job, state, flake_events, runs, round(flake_rate * 100, 1) as flake_pct, prs_affected, retry_minutes\n"
|
|
54
|
+
"from ci_jobs order by flake_events desc, failures desc limit 20"),
|
|
55
|
+
("Tests that fail most",
|
|
56
|
+
"select test_id, state, failures, flake_events, runs, message from tests order by failures desc limit 20"),
|
|
57
|
+
("Biggest open pull requests",
|
|
58
|
+
"select number, author_kind, role, additions + deletions as lines, created_at from prs where status = 'OPEN'\n"
|
|
59
|
+
"order by lines desc limit 20"),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def queries() -> list[dict]:
|
|
64
|
+
dora = []
|
|
65
|
+
for chunk in _sql_text().split("-- panel: ")[1:]:
|
|
66
|
+
name, body = chunk.split("\n", 1)
|
|
67
|
+
dora.append({"group": "DevLake dashboards", "title": DORA_TITLES[name.strip()], "sql": body.strip().rstrip(";")})
|
|
68
|
+
return (dora + [{"group": "DevLake dashboards", "title": t, "sql": s} for t, s in DEVLAKE_MORE]
|
|
69
|
+
+ [{"group": "StackVitals summaries", "title": t, "sql": s} for t, s in OURS])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
PAGE = r"""
|
|
73
|
+
<p class=mut>SQL over this repo's StackVitals data, run in your browser by DuckDB (WebAssembly, loaded from jsDelivr at a
|
|
74
|
+
pinned version with integrity hashes the first time you run a query; about 35 MB, then cached). Nothing is sent anywhere.
|
|
75
|
+
The saved queries marked DevLake are Apache DevLake's own dashboard SQL, ported to DuckDB and run on DevLake-shaped
|
|
76
|
+
tables of this repo; the dates and the DORA report fill in their time range.</p>
|
|
77
|
+
<p>From <input type=date id=sv-from> to <input type=date id=sv-to> DORA report
|
|
78
|
+
<select id=sv-report><option>2023</option><option>2021</option></select></p>
|
|
79
|
+
<div id=sv-saved></div>
|
|
80
|
+
<p><textarea id=sv-sql rows=12 style="width:100%;font-family:monospace">select * from prs limit 20</textarea></p>
|
|
81
|
+
<p><button id=sv-run>Run</button> <button id=sv-link type=button>Copy a link to this query</button>
|
|
82
|
+
<input id=sv-name placeholder="Name it" aria-label="Name for the saved query" size=18> <button id=sv-save type=button>Save in this browser</button>
|
|
83
|
+
<span id=sv-status class=mut></span></p>
|
|
84
|
+
<div id=sv-mine></div>
|
|
85
|
+
<div class=tbl><table id=sv-result class=find></table></div>
|
|
86
|
+
<p class=mut>Tables: <span id=sv-tables>reading the list...</span></p>
|
|
87
|
+
<script type="importmap">{"imports": {"apache-arrow": "https://cdn.jsdelivr.net/npm/apache-arrow@__ARROW__/+esm"}}</script>
|
|
88
|
+
<script type="module">
|
|
89
|
+
const REPO = __REPO__, SRI = __SRI__, COLUMNS = __COLUMNS__, TYPES = __TYPES__, QUERIES = __QUERIES__;
|
|
90
|
+
const BASE = "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@__DUCKDB__/dist/";
|
|
91
|
+
const $ = (id) => document.getElementById(id);
|
|
92
|
+
const status = (text) => { $("sv-status").textContent = text; };
|
|
93
|
+
const today = new Date();
|
|
94
|
+
$("sv-to").value = today.toISOString().slice(0, 10);
|
|
95
|
+
$("sv-from").value = new Date(today.getTime() - 90 * 864e5).toISOString().slice(0, 10);
|
|
96
|
+
let source = null, conn = null;
|
|
97
|
+
|
|
98
|
+
async function where() {
|
|
99
|
+
// Beside the page (Pages, public repos), next to the data (stackvitals ui, a clone), else GitHub's raw files.
|
|
100
|
+
if (source) return source;
|
|
101
|
+
for (const base of ["summary/", "../summary/"]) {
|
|
102
|
+
try {
|
|
103
|
+
const local = await fetch(base + "manifest.json", {cache: "no-store"});
|
|
104
|
+
if (local.ok) return (source = [base, await local.json()]);
|
|
105
|
+
} catch (e) { /* not served here */ }
|
|
106
|
+
}
|
|
107
|
+
const raw = `https://raw.githubusercontent.com/${REPO}/stackvitals-data/summary/`;
|
|
108
|
+
const res = await fetch(raw + "manifest.json", {cache: "no-store"});
|
|
109
|
+
if (!res.ok) throw new Error("No summaries found in this repo's stackvitals-data branch yet.");
|
|
110
|
+
return (source = [raw, await res.json()]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function connect() {
|
|
114
|
+
if (conn) return conn;
|
|
115
|
+
status("Loading DuckDB...");
|
|
116
|
+
const duckdb = await import(BASE + "duckdb-browser.mjs");
|
|
117
|
+
const bundle = await duckdb.selectBundle({
|
|
118
|
+
mvp: {mainModule: BASE + "duckdb-mvp.wasm", mainWorker: BASE + "duckdb-browser-mvp.worker.js"},
|
|
119
|
+
eh: {mainModule: BASE + "duckdb-eh.wasm", mainWorker: BASE + "duckdb-browser-eh.worker.js"}});
|
|
120
|
+
const file = (url) => url.split("/").pop();
|
|
121
|
+
const workerText = await (await fetch(bundle.mainWorker, {integrity: SRI[file(bundle.mainWorker)]})).text();
|
|
122
|
+
const wasm = await (await fetch(bundle.mainModule, {integrity: SRI[file(bundle.mainModule)]})).arrayBuffer();
|
|
123
|
+
const db = new duckdb.AsyncDuckDB(new duckdb.VoidLogger(), new Worker(URL.createObjectURL(new Blob([workerText], {type: "text/javascript"}))));
|
|
124
|
+
await db.instantiate(URL.createObjectURL(new Blob([wasm], {type: "application/wasm"})));
|
|
125
|
+
const c = await db.connect();
|
|
126
|
+
await c.query("create table calendar_months as select strftime(m, '%y/%m') as month, cast(m as date) as month_timestamp " +
|
|
127
|
+
"from range(TIMESTAMP '2000-01-01', TIMESTAMP '2100-01-01', INTERVAL 1 MONTH) t(m)");
|
|
128
|
+
conn = c;
|
|
129
|
+
conn.db = db;
|
|
130
|
+
return conn;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const loaded = new Set();
|
|
134
|
+
|
|
135
|
+
async function need(sql) {
|
|
136
|
+
// Only the tables a query names are read (a busy repo's 23 MB of summaries took 4 s all at once, 0.2 s per query after).
|
|
137
|
+
const c = await connect();
|
|
138
|
+
const [base, manifest] = await where();
|
|
139
|
+
const files = Object.keys(manifest.files || {}).filter((n) => /^[a-z0-9_]+\.jsonl$/.test(n));
|
|
140
|
+
const named = (t) => new RegExp(`\\b${t}\\b`, "i").test(sql);
|
|
141
|
+
for (const name of files) {
|
|
142
|
+
const table = name.replace(/\.jsonl$/, "");
|
|
143
|
+
const dl = table.startsWith("devlake_") ? table.slice(8) : null;
|
|
144
|
+
const as = dl && COLUMNS[dl] ? dl : table;
|
|
145
|
+
if (loaded.has(as) || !named(as)) continue;
|
|
146
|
+
status(`Reading ${as}...`);
|
|
147
|
+
const text = await (await fetch(base + name, {cache: "no-store"})).text();
|
|
148
|
+
await c.db.registerFileText(name, text);
|
|
149
|
+
// Fixed types (DevLake's, or ours for prs, ci_jobs and tests), so an all-empty column is still a number or a time.
|
|
150
|
+
const types = dl && COLUMNS[dl] ? COLUMNS[dl] : TYPES[table];
|
|
151
|
+
if (types) {
|
|
152
|
+
const spec = Object.entries(types).map(([k, t]) => `'${k}': '${t}'`).join(", ");
|
|
153
|
+
await c.query(text.trim() ? `create table "${as}" as select * from read_json('${name}', format='newline_delimited', columns={${spec}})`
|
|
154
|
+
: `create table "${as}" (${Object.entries(types).map(([k, t]) => `"${k}" ${t}`).join(", ")})`);
|
|
155
|
+
} else {
|
|
156
|
+
await c.query(text.trim() ? `create table "${table}" as select * from read_json_auto('${name}', format='newline_delimited')`
|
|
157
|
+
: `create table "${table}" (empty boolean)`);
|
|
158
|
+
}
|
|
159
|
+
loaded.add(as);
|
|
160
|
+
}
|
|
161
|
+
for (const [dl, cols] of Object.entries(COLUMNS)) {
|
|
162
|
+
// No Delivery data yet: empty tables, so DevLake's queries answer N/A instead of failing.
|
|
163
|
+
if (!loaded.has(dl) && named(dl)) {
|
|
164
|
+
await c.query(`create table ${dl} (${Object.entries(cols).map(([k, t]) => `"${k}" ${t}`).join(", ")})`);
|
|
165
|
+
loaded.add(dl);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return c;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function fill(sql) {
|
|
172
|
+
return sql.replaceAll("{{FROM}}", $("sv-from").value + " 00:00:00").replaceAll("{{TO}}", $("sv-to").value + " 23:59:59")
|
|
173
|
+
.replaceAll("{{PROJECT}}", REPO.replaceAll("'", "''")).replaceAll("{{REPORT}}", $("sv-report").value);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function cell(value, type) {
|
|
177
|
+
if (value === null || value === undefined) return "";
|
|
178
|
+
if (/Timestamp|Date/.test(type)) return new Date(Number(value)).toISOString().replace(".000Z", "Z");
|
|
179
|
+
if (typeof value === "bigint") return value.toString();
|
|
180
|
+
if (typeof value === "number") return Number.isInteger(value) ? String(value) : value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
|
|
181
|
+
return String(value);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function run() {
|
|
185
|
+
const table = $("sv-result");
|
|
186
|
+
table.textContent = "";
|
|
187
|
+
try {
|
|
188
|
+
const sql = fill($("sv-sql").value).trim().replace(/;\s*$/, "");
|
|
189
|
+
const c = await need(sql);
|
|
190
|
+
status("Running...");
|
|
191
|
+
const started = performance.now();
|
|
192
|
+
const result = await c.query(sql);
|
|
193
|
+
const fields = result.schema.fields;
|
|
194
|
+
const head = table.insertRow();
|
|
195
|
+
for (const f of fields) { const th = document.createElement("th"); th.textContent = f.name; head.appendChild(th); }
|
|
196
|
+
const rows = result.toArray();
|
|
197
|
+
for (const r of rows.slice(0, 1000)) {
|
|
198
|
+
const tr = table.insertRow();
|
|
199
|
+
for (const f of fields) tr.insertCell().textContent = cell(r[f.name], String(f.type));
|
|
200
|
+
}
|
|
201
|
+
status(`${rows.length} row${rows.length === 1 ? "" : "s"} in ${Math.round(performance.now() - started)} ms` +
|
|
202
|
+
(rows.length > 1000 ? " (first 1000 shown)" : ""));
|
|
203
|
+
} catch (e) {
|
|
204
|
+
status("Error: " + (e && e.message ? e.message : e));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const saved = $("sv-saved");
|
|
209
|
+
let group = null;
|
|
210
|
+
for (const q of QUERIES) {
|
|
211
|
+
if (q.group !== group) { group = q.group; const h = document.createElement("h3"); h.textContent = group; saved.appendChild(h); }
|
|
212
|
+
const b = document.createElement("button");
|
|
213
|
+
b.textContent = q.title;
|
|
214
|
+
b.style.margin = "0 6px 6px 0";
|
|
215
|
+
b.onclick = () => { $("sv-sql").value = q.sql; run(); };
|
|
216
|
+
saved.appendChild(b);
|
|
217
|
+
}
|
|
218
|
+
$("sv-run").onclick = run;
|
|
219
|
+
|
|
220
|
+
// Your own queries, kept in this browser only (storage can be off: then they last until the tab closes).
|
|
221
|
+
const KEY = "sv-explore:" + REPO;
|
|
222
|
+
let mine = [];
|
|
223
|
+
try { mine = JSON.parse(localStorage.getItem(KEY) || "[]"); } catch { mine = []; }
|
|
224
|
+
function keep() { try { localStorage.setItem(KEY, JSON.stringify(mine)); } catch { /* storage off */ } }
|
|
225
|
+
function showMine() {
|
|
226
|
+
const box = $("sv-mine");
|
|
227
|
+
box.textContent = "";
|
|
228
|
+
if (!mine.length) return;
|
|
229
|
+
const h = document.createElement("h3");
|
|
230
|
+
h.textContent = "Your queries";
|
|
231
|
+
box.appendChild(h);
|
|
232
|
+
mine.forEach((q, i) => {
|
|
233
|
+
const b = document.createElement("button");
|
|
234
|
+
b.textContent = q.title;
|
|
235
|
+
b.style.margin = "0 2px 6px 0";
|
|
236
|
+
b.onclick = () => { $("sv-sql").value = q.sql; run(); };
|
|
237
|
+
const x = document.createElement("button");
|
|
238
|
+
x.textContent = "×";
|
|
239
|
+
x.title = "Forget " + q.title;
|
|
240
|
+
x.setAttribute("aria-label", "Forget " + q.title);
|
|
241
|
+
x.style.margin = "0 10px 6px 0";
|
|
242
|
+
x.onclick = () => { mine.splice(i, 1); keep(); showMine(); };
|
|
243
|
+
box.append(b, x);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
$("sv-save").onclick = () => {
|
|
247
|
+
const title = $("sv-name").value.trim() || "Query " + (mine.length + 1);
|
|
248
|
+
mine = [...mine.filter((q) => q.title !== title), { title, sql: $("sv-sql").value }];
|
|
249
|
+
keep();
|
|
250
|
+
showMine();
|
|
251
|
+
status("Saved in this browser as " + title + ".");
|
|
252
|
+
};
|
|
253
|
+
// A link that opens this page with the query ready to run: other pages link here the same way to drill down.
|
|
254
|
+
$("sv-link").onclick = async () => {
|
|
255
|
+
const url = location.href.split("#")[0] + "#sql=" + encodeURIComponent($("sv-sql").value);
|
|
256
|
+
try { await navigator.clipboard.writeText(url); status("Link copied."); } catch { status(url); }
|
|
257
|
+
};
|
|
258
|
+
showMine();
|
|
259
|
+
// Loaded, never run: a link from someone else is read before it runs, and a broken one leaves the page working.
|
|
260
|
+
if (location.hash.startsWith("#sql=")) {
|
|
261
|
+
try {
|
|
262
|
+
$("sv-sql").value = decodeURIComponent(location.hash.slice(5));
|
|
263
|
+
status("Query from the link: read it, then run it.");
|
|
264
|
+
} catch { status("The link's query couldn't be read."); }
|
|
265
|
+
}
|
|
266
|
+
where().then(([base, m]) => {
|
|
267
|
+
$("sv-tables").textContent = Object.entries(m.files || {}).map(([n, f]) =>
|
|
268
|
+
`${n.replace(/\.jsonl$/, "").replace(/^devlake_/, "")} (${f.rows} rows)`).join(", ") +
|
|
269
|
+
(base.startsWith("http") ? " - read from GitHub" : " - read from this folder");
|
|
270
|
+
}, (e) => { $("sv-tables").textContent = e.message; });
|
|
271
|
+
</script>
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def render(repo: str, pages=()) -> str:
|
|
276
|
+
from stackvitals import shell
|
|
277
|
+
body = (PAGE.replace("__REPO__", json.dumps(repo)).replace("__SRI__", json.dumps(SRI))
|
|
278
|
+
.replace("__COLUMNS__", json.dumps(COLUMNS)).replace("__TYPES__", json.dumps(TYPES)).replace("__QUERIES__", json.dumps(queries()).replace("</", "<\\/"))
|
|
279
|
+
.replace("__DUCKDB__", DUCKDB).replace("__ARROW__", ARROW))
|
|
280
|
+
return shell.page("Explore", repo, "explore.html", "SQL over this repo's data, in your browser.", body, pages)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def write_explore(root: Path, repo: str) -> bool:
|
|
284
|
+
"""docs/explore.html (it says when there are no summaries yet); rewritten only when its text changes (no churn)."""
|
|
285
|
+
from stackvitals import shell
|
|
286
|
+
docs = root / "docs"
|
|
287
|
+
docs.mkdir(parents=True, exist_ok=True)
|
|
288
|
+
path = docs / "explore.html"
|
|
289
|
+
text = render(repo, shell.present(docs) | {"explore.html"})
|
|
290
|
+
try:
|
|
291
|
+
if path.read_text(encoding="utf-8") == text:
|
|
292
|
+
return False
|
|
293
|
+
except OSError:
|
|
294
|
+
pass
|
|
295
|
+
path.write_text(text, encoding="utf-8", newline="\n")
|
|
296
|
+
return True
|