AppPlatform: Introduce experimental Github integration for dashboard configuration management (#96329)
* [Provisioning] Pay back some technical debt (#100720) * Handle pagination in github client * Add some unit test coverage * Remove unknown repository * Remove unknown leftover * Revert "Add some unit test coverage" This reverts commit420c9674d2. * Revert "Revert "Add some unit test coverage"" This reverts commitf7eca41957. * Revert unit tests in github package * Remove S3 case as it's now deprecated * [Provisioning] Consolidate job status report in JobProgressRecorder (#100718) * Log also successful operation * Consolidate stop logic under TooManyErrors * Use error for TooManyErrors * Pass the progress recorder * Define JobProgressRecorder interface * Do not expect workers to return status * Remove scenarios due to pointers * Use recorder to manage the entire state * Provisioning: Support rotating secrets (#100705) * Provisioning: Refactor webhook to another interface (#100733) * POC/Provisioning: Remove S3 references (#100734) * Remove unused script * Remove s3 references * Provisioning: Keep the existing k8s name if it is specified in metadata (#100672) * keep name * keep name * Revert "keep name" This reverts commit29f87bcaeb. * Commit stale go.mod * Keep name also for sync deletions --------- Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> * Update preview banner copy * Query Library: Move backend to enterprise (#100371) * Fix wire * Fix jobs table re-renders * Provisioning: Refactor history to its own interface (#100735) * Provisioning: Refactor history to its own interface * refactor: use VersionedRepository * Update API * Provisioning: Test the GitHub client directly (#100808) * Provisioning: Test the GitHub client directly Instead of mocking the abstracted client, test it as well by mocking the underlying GitHub client. This also lets us remove the mock for the abstracted client. * refactor: move out helpers * chore: set dependency owner * Provisioning: Better clone/push error support (#100854) * Provisioning: Replace searcher with one that knows about modes (#100857) * Provisioning: Start in "mode5" when nothing exists in legacy (#100862) * [Provisioning] Fix duplicate sync jobs triggered in controller (#100870) * Improve logging on reasons why the controller triggered * Fix messaging for sync job * fix lint * Provisioning: Move legacy export/import into a single migrate job (#100865) * [Provisioning] Miscellanenous bug fixes and improvements (#100976) * Error if found duplicate ID * Fix issue with manual test button * Fix issue with health errors not going away * Display status in sync overview * Use patch operations instead * Trigger sync job after status update * Convert Export Tab into modal * Remove unused FieldSet import * Only last 8 jobs * Remove Links card * Use button for Github Source Code * Add actions to resources page * Add resource column to Repository Resources * Display Job Spec in RecentJobs * Display dates in history page * Display Avatar if available * Improve styling of the avatar * Update betterer * Remove duplicate history header in history * Commit betterer * Address code styling issues * update flags * github v69 * v69 * POC/Provisioning: Add wizard (#100596) * Chore: make update-workspace * Chore: Fix lints (#101039) * Provisioning: Workflows as write access (#101031) * workflow as write access * workflow as write access * workflow as write access * Update pkg/registry/apis/provisioning/repository/test.go Co-authored-by: Mariell Hoversholm <mariell.hoversholm@grafana.com> * POC/Provisioning: Add wizard (#100596) * update refs * update refs * lint fix * lint fix * lint fix * default everythign to read only * reuse form components * remove main --------- Co-authored-by: Mariell Hoversholm <mariell.hoversholm@grafana.com> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * merge main * Fix workflow types * Betterer * [Provisioning] Fix webhook and finalizer issues (#101052) Fix webhook and finalizer issue The maximum number of webhooks per repository is 20 * [Provisioning] Fix issue with last ref (#101056) * Fix issue with last ref * Update frontend code * Fix the local tmp test * Use lastRef * POC/Provisioning: Simplify connect step (#101064) * Fix sending workflows * Use write for local * Move connect action to the next step * Remove wizard props * Typo * Redirect to wizard * Show repo link after successful export * Provisioning: Avoid starting sync jobs when using legacy storage (#101114) * avoid starting sync jobs on legacy * newlines * Provisioning: Onboarding landing page (#101112) * add landing page before wizard * Update onboarding page * Update URL * Remove unused * Add deleteAll button * Improved text * betterer --------- Co-authored-by: Clarity-89 <homes89@ukr.net> * Provisioning: use the sync job to finish the migrate job (#101107) * Provisioning: Show progress more often (#101128) * show progress bar earlier * show progress bar earlier * update wording to be less specific * POC/Provisioning: Enable sync (#101131) * update preview banner * actualy remove and don't crash without provisioning flag * Update db banner * Provisioning: Export oldest items first (#101189) * Provisioning: better branch handling (#101188) * add missing file * Provisioning: Fix tests (#101197) * Provisioning: Refactor tests to be multiple functions * Provisioning: Fix tests * fix: make github-example sync * fix misspell * Provisioning: avoid migration wizard if things are already in unified storage (#101204) Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * more lint * POC/Provisioning: Handle connect step errors (#101192) * Provisioning: Unify status handling in repository and migrate steps * Refactor: Move WorkflowsField outside RepositoryStep and use proper type * Refactor: Improve repository verification error handling and UI * Refactor: Simplify repository verification error handling * Refactor: Simplify RepositoryStep component structure * Refactor: Improve error handling in RepositoryStep * Refactor: Remove redundant repository creation logic from ProvisioningWizard * Refactor: Simplify RequestErrorAlert component * show github error * now will verify * test .git * recover from bad config * Update error handling * Remove unused prop * merge upstream * Show migration summary * Update text * Improve text * Betterer * [Provisioning] Review controller changes (#101216) * Review health check conditions * Move down the logic to set up the sync status * Skip if it's only a health check rerun * Fix health check conditions * Preserve last ref * Format code * Rename to shouldSkipSync --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Roberto Jiménez Sánchez <roberto.jimenez@grafana.com> * fix promotion step * In the promotion pipeline, publish should depend on build * fix promotion pipeline * [Provisioning] Use smaller methods to process repository events (#101240) * update codegen * merge main * Provisioning: Avoid localhost error loop (#101253) * Provisioning: Update the recent jobs formatting (#101250) format history * [Provisioning] Refactor Pull Request & Lint worker (#101273) * Refactor the code * Refactor into separate files * Consolidate linter flag in one spot * Use global feature flags * Commit betterer * Remove from JSON the intermidiate flag * Use again spec * Clean up * Revert changes in test * POC/Provisioning: Remove sync confirm modal (#101281) * [Provisioning] Remove linting from MVP (#101286) * Remove Linting backend * Re-generate client * POC/Provisioning: Unify tags (#101218) * Unify tags * add both tags * add tag types * Check for the redirect only once * Add fetch settings with delay hook * Refetch settings * Split hooks into separate files * Cleanup * Prettier * Prettier * Remove lint code * Betterer --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Provisioning: Update token instructions (#101280) * Provisioning: Use blob storage rather than local file system to save images (#101298) * [Provisioning] Add more explicit setup warnings if webhook integration and image rendering are disabled (#101304) * Setup warnings if renders or webhooks are not possible * Improve display * Use a single Alert * Make design more compact * Only display local config is missing critical feature toggles * Improve styling of required flags * Add file name to the custom ini itself * Add copy button * Add FeatureSection Component * Commit betterer * Use an interactive table * Use a modal for instructions * Use the same modal for example config * Improve setup steps * Improve stepper * Copy code ctrl + c * Make it more compact * Select feature to enable * Improve the height of alerts * Separate components * Better warnings * Improve the page * Improve the cards * Improve cards even more * Improve cards * Improve cards * Optional copy in code block * Add side bar with steps * Improve styling * Style modal * Clean up Code * Remove index file * Simplify Step Component * Commit betterer * Simplify components * Use CodeEditor and Clipboard components * Do not show scrollback on minimum size * Fix positioning of footer * Separate Component for Feature * Use different styling * Commit betterer * Use more Grafana components in the FeatureCard * Separate sidebar into own components * Simplify sidebar code * Commit betterer * Remove connector * Simplify styling further * Use cards * Improve code * Use more grafana component in InstructionsModal * Further simplify * Simplify the code * Simplify style * Clean up * Simplify the Wizard * Use little icons * Improve feature cards * Improve cards * Commit betterer * Add description to feature setup * Improve instructions for snapshot preview * Move all files into Setup folder * Commit betterer * Clean up the warnings code * Improve coding * Move sidebar item to separate fiel * Rename components * Fix issues * Use stack instead * Improve style * Don't show setup button if configured already * Simplify again CSS * Use secondary actions * Style a bit more * Improve wording * Update warning * Refer to docs in Image Renderer * More clean up * Revert changes in generated client * Fix typos and imports * Fix lint errors * Provisioning: better error support (#101490) * update openapi snapshot * fix build * Provisioning: Only show setup page when feature toggles are missing (#101502) * form fixing * form fixing * always send UID * Same onboarding page regarless of migration (#101557) * backend building... frontend still broken * rename sync with main * Provisioning: Update dashboard badge (#101599) * Rename to push / pull everything user-facing (#101577) * Rename to push / pull all everything user-facing * Use automatic pulling wording * Provisioning: Migrate when using unified storage (#101572) * migrate when not unified * Update pkg/registry/apis/provisioning/register.go Co-authored-by: Roberto Jiménez Sánchez <roberto.jimenez@grafana.com> * variables * merge main --------- Co-authored-by: Roberto Jiménez Sánchez <roberto.jimenez@grafana.com> * Add tabs and features tab to listing page (#101570) * List features in onboarding page (#101558) * merge main * POC/Provisioning: Check if the instance is provisioned (#101601) * Check if the instance is provisioned * Fix lints * Fix getting config for new dashboard * Fixes after merge * More fixes * Show success message * Fix default value * Add test * Fix lints * Provisioning: Include URLs in ResourceWrapper response (#101511) * Convert Migrate wizard into a Connection Wizard (#101575) * Convert Migrate wizard into a Connection Wizard * Remove duplicate empty state * Allow users to select target in the first step * Remove file created by merge * Select target based on existing connections * Default option for targets and explainatory alert * Do not display connect button if single connection * Display target as tag in repository card * Add Pull Step * Fix linting * User decides if migrate or connect * Improve style based based on review * Provisioning: Return upsert resource when writing (#101574) * [Provisioning] Getting Started Page and Tab (#101701) * merge main * fix go.mod * Provisioning: Redirect to the new URL after save (#101757) Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * POC/Provisioning: create folder (#101619) * Add NewProvisionedFolderForm * Cleanup * Add folder api * Register API * Do not show provisioned badge for instances * Enable saving * Show saved alert * Fixes * Fix deps * Cleanup * Add test * Add test * Updates * Fix test * Fix import * [Provisioning] Display instance connection directly in home (#101720) * Show tabs directly in home page for instance sync * Display actions also in the home page * Add delete button to actions * Fix issue with files tab * Display tabs also in instant sync * Generate connection name for instance sync * Fix issues when no repositories * Set active tab * Remove leftover * Rename component for listing folder repository * Fix linting issues * Fix merge error * Rename to HomePage * Split folder list into separate component * Create own component for repository card * Improve RepositoryCard readability * Improve RepositoryCard styling * Make view primary button * Fix syntax * Fix generateName * merge main * Provisioning: Move folder management into its own helper (#101864) * folder * update folder links * Fix test * update * cleanup --------- Co-authored-by: Clarity-89 <homes89@ukr.net> * fixed folder issue * POC/Provisioning: Create folder from root (#101921) * Enable creating folder at the root * Fix test * Add interceptor * Provisioning: Expose stats (#101927) * [Provisioning] Unified onboarding wizard (#101952) * Spike the solution * More work * Add more situations * Attempt to display count of dashboards and folders * Attempt with file count * Do not display options if not possible * Improve styling resources * Use another API * Fix issue with selection * Style a bit * Fix more issues * Make the sync step work * Improve links * Use LinkButton * Start pull automatically * Start migration automatically * Fix issue with options * Fix issues * Fix loading error * Improve more things * Improve styling * Improve messaging * Set the autofocus * Fix some issues * Fix issue with disabled options * Only resources * Finish settings depending on configuration * Move title to wizard * Fix title * Improve styling * Badge * Explain on hover * Improve styling * Disabled at the bottom * History & identifiers * Improve wording * Add padding left and right disable options * Delete repository * Improve buttons * Give index time to catch up * Improve buttons * Handle steps with only forms * Fix issue with initial migrate or pull * Commit betterer * Error messages * Use memo * Revampt that a bit * Attempt to simplify the state and components * Improve the component for Migrate * Commit betterer * Fix issue in next button * Clean up more * Start for boostrap step * Fix issue with running status * Fix issue with loading bootstrapping * Improve loading * Improve more the loading * Fix issue with loading * Empty tree * Handle error * Fix issue with looping * Remove commented out lines * Add comment * Remove accidental file * Fix imports * Improve MigrateStep and PullStep * Use hook for step status * JobStep component * Refactor data fetching * Validate with Github * Fix issue with failed error * Fix next on success * Address small comments * Separate file for WizardContent * Fix linting * Use step approach also for bootstrap * Make the logic for moving between steps clearer * Fix navigation issue * Clean up some logic * Use useAsync for JobStep steps * Revert "Use useAsync for JobStep steps" This reverts commit242a275cc9. * Provisioning: use service to get counts (#101972) counts * must migrate when using legacy storage * Revert "Revert "Use useAsync for JobStep steps"" This reverts commita420d0ac36. * Fix async conditions * Organize imports * Separate component for BootstrapOptionCard * BootstrapOptionsList * Remove duplicate definitions --------- Co-authored-by: Clarity-89 <homes89@ukr.net> Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Merge * Fix utils * POC/Provisioning: Fix folder path for dashboard (#101997) * Provisioning: Fix folder path for dashboards * Fix isNew * Update test * Fix any error * Betterer * [Provisioning] Improve progress recording and updates (#102035) * do not validate on delete * Provisioning: Implement authorizer for remaining resources (#101945) * feat: implement authorizer for remaining resources * fix: don't allow viewers to write files * security: harden blob id fetching * add integration test for admin vs viewer * feat: only Get is a valid verb for reads in our subresources Co-Authored-By: Ryan McKinley <ryantxu@gmail.com> * feat: allow render for all requests * refactor: use guards Not changing code that goes `if a { } else if b { } else { }` as the semantic meaning of the different branches is easier to parse. --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * POC/Provisioning: Improve wizard setup (#102066) * wizard actions * workign better * remove more memo * show polling interval * cleanup * finalizers * Update public/app/features/provisioning/Wizard/BootstrapStep.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/BootstrapStep.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/BootstrapStep.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/BootstrapStep.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/BootstrapStep.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/WizardContent.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/WizardContent.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Update public/app/features/provisioning/Wizard/BootstrapStep.tsx Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * updates from alex * updates from alex * Simplify actions * Extract props * history supported form legacy only (for now) * More refactor * change order * Fix cleanup finalizer * show kinds * fix lint --------- Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Clarity-89 <homes89@ukr.net> Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> * Fix resource condition (#102086) * Fix job summary stats issue (#102084) * merge main * Repository link should point to configured branch (#102092) * Add getRepoHref * Fix random string generation * Redirect to Home on repository deletion (#102096) * Fix extra commas in pull request comment (#102108) * Fix image rendering endpoint (#102107) * POC/Provisioning: Support migrate... when starting with unified storage (#102097) * use same clone * now using upsert * Fix lint --------- Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> * Clean up unprovisioned resources after unified storage migration (#102126) * Clean up unprovisioned resources after migrate * Clean up unprovisioned resources after migrate * Update pkg/registry/apis/provisioning/jobs/migrate/resources.go * Reset summary between export and pull (#102101) * Reset summary between export and pull * Add reset results to unified storage migration * Provisioning: always dirty (#102151) * fix test version * log the watch line * POC/Provisioning: Disable repository list watch (#102169) * Disable watch for repo list endpoint * Add comment * Remove another watch * Provisioning: Avoid calling test on every update (#102161) test less often * Provisioning: Support prefixes in GitHub repositories (#101969) * feat: add a Prefix property to GitHub repo spec * feat: make nested folders work properly * feat: use subdir for go-git export * fix: placeholder for prefix should be grafana/ * feat: rename prefix to path * fix: json name should be path, too Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * chore: regen apis * fix: copy 'path' --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Merge * Fix duplicate type * Provisioning: Test export functionality (#101336) * Provisioning: Test export functionality * fix: use context.Background * test: add more cases for local repo path resolving * test: rework test inputs * test: try to make github test work * fix: clear global state * Update api client imports * Fix import * Fix test * Update codegen * Provisioning: Make it green (#102271) * chore: remove unused functions * chore: update betterer results * chore: update openapi spec * chore: yarn generate-apis * fix: specify default false if undefined * Use AnnoKeyManagerIdentity * Add manager kind * POC/Provisioning: Update component structure (#102297) * Update project structure * Update imports * Remove unused components * Copy fixes * Typo * More copy fixes * Betterer * Update test * merge main * Provisioning: Replace hardcoded clients with discovery client (#101918) * disco client * discovery client * merge main * merge main * keep factory * keep factory * find preffered version for delete factory * use same folders request * merge main * with integration test * POC/Provisioning: Compare spec in test rather than raw JSON (#102352) * compare spec not json * compare spec not json * [Provisioning] Add in-code TODOs in API Server area (#102360) * Add TODOs for files endpoint * Add TODO history endpoint * Add TODO to move files logic to resource package * Add TODO to not use private fields directly * Remove unnecessary checks in list connector * Add pagination TODO in lister * Add TODO to rename resources * Add todo about cloning too early * Add TODO to propose to merge sync and migrate endpoints * Add TODOs in register * Add more TODOs in connectors & routes * Add TODOs about prefix * Change it to remove * Update pkg/registry/apis/provisioning/test.go Co-authored-by: Ryan McKinley <ryantxu@gmail.com> --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * POC/Provisioning: Switch folders to use managedBy (#102362) * Folders: Switch to managedBy * Fix create folder * Fix tests * Do not allow changing folder from dashboard settings * Update imports * Update provisioned meta * Do not show provisioned badge for child folders * Fix folder title * Update folder actions * Update new provisioned folder form * Remove unused code * Fix condition * Reset default values on change * Remove duplicate nav item * Add managedBy to DashboardQueryResult * Provisioning: support watch over live (in feature branch) (#102408) * Provisioning: watch cleanup (#102424) * fix lint * Provisioning: Add basic usage stats (#102405) * [Provisioning] Add limitations to Github Repository (#102451) * Put limits to Github * File is too large * Move constants * Embed ListOptions again * Remove TODO * Provisioning: Pick a better default title (#102516) better title * Provisioning: sanitize pull request urls (#102517) * [Provisioning] Clean up clone after export and migrate (#102467) * Remove clone directory on clone failure * Defer remove clones * Log error if removal fails --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * [Provisioning] Limit path length and depth in APIs (#102472) * Limit filepath length in files API calls * Add common utility to deal with paths * Use the existing function * Fix import * Update pkg/registry/apis/provisioning/safepath/limit.go Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Fix issue after website commit * Fix linting issue in test --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Add timeout, JSON check and max body size to endpoints (#102443) * Add timeout, JSON check and max body size to endpoints * Use http.MaxBytesReader instead * Use MaxBytesReader also for reading the entire body * Add empty line * Add unit tests * Fix integration tests * Update pkg/registry/apis/provisioning/render.go Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * Do not use utils :) * Fix comment on unmarshalJSON * 25MB for webhook events * Remove content type check for files write --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * fix imports * Provisioning: Remove export option from the UI (#102511) * [Provisioning] Remove unused checkout method in go-git (#102460) * [Provisioning] Limit max number of repositories to 10 (#102542) * Limit to maximum 10 repositories in backend * Change messaging * Do not display connect button if more than 10 * Only fetch settings once * watch repos --------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com> * [Provisioning] Limit size and time to git clone and push in go-git (#102458) * Limit git clone and push time and size * Fix linting * Use transport instead to limit * Remove not supported * Add TODO to make timeout configurable * chore: make update-workspace * Provisioning: Implement a new job queue (#102446) * feat: implement a new job queue Outstanding problems: * Status isn't saved. * Progress updates don't work (due to status not being saved probably?). * feat: properly save status * chore: document label * chore: assumptions do hold * fix: support multi-tenant job drivers * fix: use namespace=* * fix: set resource back to pointer when updating job progress If we don't do this, we start rejecting job progress updates as the version falls out of sync. * feat: make job APIs read-only * fix: complete job when worker returns * fix: set namespace on requests from controller * test: check historic jobs * chore: regen apis * feat: start augmenting frontend * feat: add jobs to authorizer * feat: use watch from input * fix: make frontend subscribe to historic jobs * fix: lint * chore: yarn prettier:write * fix: frontend lints * test: allow for empty state in historicjobs * test: set content type for export request * fix: always set job name on insert * fix: import * fix: use dashes not colons * fix: job status should expect a historic job transition * fix: allow PR jobs from multiple PRs * feat: same name for sync and migrate jobs * feat: generate a job name in the store * refactor: rename to persistentStore * feat: remove status subresources on jobs * feat: join jobs into one card * chore: regen openapi snapshot --------- Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com> * Provisioning: Use a complete storage for jobs (#102605) * feat: add a complete strategy to apiserver * feat: use the complete storage strategy for jobs * test: behaviour changed in main * [Provisioning] Consolidate file path handling (#102617) * Add more cases for validation * Call the method dir * Clean files endpoint * Simplify further * Fix issues with folder * Add Dir function * Use walk function in folders * Move things from ID * Fix some tests * Add tree * Sync worker and changes * Add more TODOs * Add normal join * Remove things in local * Consolidate single Join * Call it safe * Add new IsPathSupported action * Move the depth to resources * Add more cases * Improve trie implementation * Add tests trie * Fix trie tests * Improve trie tests * Add tests for walk * Fix linting * Add unit tests filepath * Remove TODO * Remove another TODO * Unsupported file extension error * Add documentation for IsPathSupported * Filepath unit tests * Use safepath to validate github path * Remove TODO in wrapper * Use trailing slash in folder internal object * Fix changes test * Include dot * Add TODO to explore own type for path * Fix frontend lint * Fix unit tests * Fix provisioning integration tests --------- Co-authored-by: Roberto Jiménez Sánchez <roberto.jimenez@grafana.com> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Clarity-89 <homes89@ukr.net> Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com> Co-authored-by: Kevin Minehart <kmineh0151@gmail.com>
This commit is contained in:
co-authored by
Ryan McKinley
Alex Khomenko
Clarity-89
Roberto Jimenez Sanchez
Andrej Ocenas
Kevin Minehart
parent
e48ec7d6b6
commit
77fa2271be
@@ -0,0 +1,182 @@
|
||||
// The github package exists to provide a client for the GH API, which can also be faked with a mock.
|
||||
// In most cases, we want the real client, but testing should mock it, lest we get blocked from their API, or have to configure auth for simple tests.
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// API errors that we need to convey after parsing real GH errors (or faking them).
|
||||
var (
|
||||
ErrResourceAlreadyExists = errors.New("the resource already exists")
|
||||
ErrResourceNotFound = errors.New("the resource does not exist")
|
||||
ErrMismatchedHash = errors.New("the update cannot be applied because the expected and actual hashes are unequal")
|
||||
ErrNoSecret = errors.New("new webhooks must have a secret")
|
||||
//lint:ignore ST1005 this is not punctuation
|
||||
ErrPathTraversalDisallowed = errors.New("the path contained ..") //nolint:stylecheck
|
||||
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
||||
ErrFileTooLarge = errors.New("file exceeds maximum allowed size")
|
||||
ErrTooManyItems = errors.New("maximum number of items exceeded")
|
||||
)
|
||||
|
||||
// MaxFileSize maximum file size limit (10MB)
|
||||
const MaxFileSize = 10 * 1024 * 1024 // 10MB in bytes
|
||||
|
||||
type ErrRateLimited = github.RateLimitError
|
||||
|
||||
type Client interface {
|
||||
// IsAuthenticated checks if the client is authenticated.
|
||||
IsAuthenticated(ctx context.Context) error
|
||||
|
||||
// GetContents returns the metadata and content of a file or directory.
|
||||
// When a file is checked, the first returned value will have a value. For a directory, the second will. The other value is always nil.
|
||||
// If an error occurs, the returned values may or may not be nil.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error)
|
||||
|
||||
// GetTree returns the Git tree in the repository.
|
||||
// When recursive is given, subtrees are mapped into the returned array.
|
||||
// When basePath is given, only trees under it are given. The results do not include this path in their names.
|
||||
//
|
||||
// The truncated bool will be set to true if the tree is larger than 7 MB or 100 000 entries.
|
||||
// When truncated is true, you may wish to read each subtree manually instead.
|
||||
GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) (entries []RepositoryContent, truncated bool, err error)
|
||||
|
||||
// CreateFile creates a new file in the repository under the given path.
|
||||
// The file is created on the branch given.
|
||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
||||
// The content is what the file should contain. An empty slice is valid, though often not very useful.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error
|
||||
|
||||
// UpdateFile updates a file in the repository under the given path.
|
||||
// The file is updated on the branch given.
|
||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
||||
// The content is what the file should contain. An empty slice is valid, though often not very useful.
|
||||
// If the path does not exist, an error is returned.
|
||||
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error
|
||||
|
||||
// DeleteFile deletes a file in the repository under the given path.
|
||||
// The file is deleted from the branch given.
|
||||
// The message given is the commit message. If none is given, an appropriate default is used.
|
||||
// If the path does not exist, an error is returned.
|
||||
// The hash given must be the SHA hash of the file contents. Calling GetContents in advance is an easy way of handling this.
|
||||
//
|
||||
// If ".." appears in the "path", this method will return an error.
|
||||
DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error
|
||||
|
||||
// Commits returns the commits for the given path
|
||||
Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error)
|
||||
|
||||
// CompareCommits returns the changes between two commits.
|
||||
CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error)
|
||||
|
||||
// RepoExists checks if a repository exists.
|
||||
RepoExists(ctx context.Context, owner, repository string) (bool, error)
|
||||
|
||||
// CreateBranch creates a new branch in the repository.
|
||||
CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error
|
||||
// BranchExists checks if a branch exists in the repository.
|
||||
BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error)
|
||||
// GetBranch returns the branch of the repository.
|
||||
GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error)
|
||||
|
||||
ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error)
|
||||
CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error)
|
||||
GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error)
|
||||
DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error
|
||||
EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error
|
||||
|
||||
ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error)
|
||||
CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error
|
||||
CreatePullRequestFileComment(ctx context.Context, owner, repository string, number int, comment FileComment) error
|
||||
ClearAllPullRequestFileComments(ctx context.Context, owner, repository string, number int) error
|
||||
}
|
||||
|
||||
type RepositoryContent interface {
|
||||
// Returns true if this is a directory, false if it is a file.
|
||||
IsDirectory() bool
|
||||
// Returns the contents of the file. Decoding happens if necessary.
|
||||
// Returns an error if the content represents a directory.
|
||||
GetFileContent() (string, error)
|
||||
// Returns true if this is a symlink.
|
||||
// If true, GetPath returns the path where this symlink leads.
|
||||
IsSymlink() bool
|
||||
// Returns the full path from the root of the repository.
|
||||
// This has no leading or trailing slashes.
|
||||
// The path only uses '/' for directories. You can use the 'path' package to interact with these.
|
||||
GetPath() string
|
||||
// Get the SHA hash. This is usually a SHA-256, but may also be SHA-512.
|
||||
// Directories have SHA hashes, too (TODO: how is this calculated?).
|
||||
GetSHA() string
|
||||
// The size of the file. Not necessarily non-zero, even if the file is supposed to be non-zero.
|
||||
GetSize() int64
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
Name string
|
||||
Sha string
|
||||
}
|
||||
|
||||
type CommitAuthor struct {
|
||||
Name string
|
||||
Username string
|
||||
AvatarURL string
|
||||
}
|
||||
|
||||
type Commit struct {
|
||||
Ref string
|
||||
Message string
|
||||
Author *CommitAuthor
|
||||
Committer *CommitAuthor
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type CommitFile interface {
|
||||
GetSHA() string
|
||||
GetFilename() string
|
||||
GetPreviousFilename() string
|
||||
GetStatus() string
|
||||
}
|
||||
|
||||
type FileComment struct {
|
||||
Content string
|
||||
Path string
|
||||
Position int
|
||||
Ref string
|
||||
}
|
||||
|
||||
type CreateFileOptions struct {
|
||||
// The message of the commit. May be empty, in which case a default value is entered.
|
||||
Message string
|
||||
// The content of the file to write, unencoded.
|
||||
Content []byte
|
||||
}
|
||||
|
||||
type WebhookConfig struct {
|
||||
// The ID of the webhook.
|
||||
// Can be 0 on creation.
|
||||
ID int64
|
||||
// The events which this webhook shall contact the URL for.
|
||||
Events []string
|
||||
// Is the webhook enabled?
|
||||
Active bool
|
||||
// The URL GitHub should contact on events.
|
||||
URL string
|
||||
// The content type GitHub should send to the URL.
|
||||
// If not specified, this is "form".
|
||||
ContentType string
|
||||
// The secret to use when sending events to the URL.
|
||||
// If fetched from GitHub, this is empty as it contains no useful information.
|
||||
Secret string
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// Factory creates new GitHub clients.
|
||||
// It exists only for the ability to test the code easily.
|
||||
type Factory struct {
|
||||
// Client allows overriding the client to use in the GH client returned. It exists primarily for testing.
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func ProvideFactory() *Factory {
|
||||
return &Factory{}
|
||||
}
|
||||
|
||||
func (r *Factory) New(ctx context.Context, ghToken string) Client {
|
||||
if r.Client != nil {
|
||||
return NewClient(github.NewClient(r.Client))
|
||||
}
|
||||
|
||||
tokenSrc := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: ghToken},
|
||||
)
|
||||
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
||||
return NewClient(github.NewClient(tokenClient))
|
||||
}
|
||||
@@ -0,0 +1,770 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v69/github"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/provisioning/safepath"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type githubClient struct {
|
||||
gh *github.Client
|
||||
}
|
||||
|
||||
var _ Client = (*githubClient)(nil)
|
||||
|
||||
func NewClient(client *github.Client) *githubClient {
|
||||
return &githubClient{client}
|
||||
}
|
||||
|
||||
func (r *githubClient) IsAuthenticated(ctx context.Context) error {
|
||||
if _, _, err := r.gh.Users.Get(ctx, ""); err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) {
|
||||
switch ghErr.Response.StatusCode {
|
||||
case http.StatusUnauthorized:
|
||||
return apierrors.NewUnauthorized("token is invalid or expired")
|
||||
case http.StatusForbidden:
|
||||
return &apierrors.StatusError{
|
||||
ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusUnauthorized,
|
||||
Reason: metav1.StatusReasonUnauthorized,
|
||||
Message: "token is revoked or has insufficient permissions",
|
||||
},
|
||||
}
|
||||
case http.StatusServiceUnavailable:
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubClient) RepoExists(ctx context.Context, owner, repository string) (bool, error) {
|
||||
_, resp, err := r.gh.Repositories.Get(ctx, owner, repository)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
const (
|
||||
maxDirectoryItems = 1000 // Maximum number of items allowed in a directory
|
||||
maxTreeItems = 10000 // Maximum number of items allowed in a tree
|
||||
maxCommits = 1000 // Maximum number of commits to fetch
|
||||
maxCompareFiles = 1000 // Maximum number of files to compare between commits
|
||||
maxWebhooks = 100 // Maximum number of webhooks allowed per repository
|
||||
maxPRFiles = 1000 // Maximum number of files allowed in a pull request
|
||||
maxPullRequestsFileComments = 1000 // Maximum number of comments allowed in a pull request
|
||||
maxFileSize = 10 * 1024 * 1024 // 10MB in bytes
|
||||
)
|
||||
|
||||
func (r *githubClient) GetContents(ctx context.Context, owner, repository, path, ref string) (fileContents RepositoryContent, dirContents []RepositoryContent, err error) {
|
||||
// First try to get repository contents
|
||||
opts := &github.RepositoryContentGetOptions{
|
||||
Ref: ref,
|
||||
}
|
||||
|
||||
fc, dc, _, err := r.gh.Repositories.GetContents(ctx, owner, repository, path, opts)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return nil, nil, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return nil, nil, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return nil, nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if fc != nil {
|
||||
// Check file size before returning content
|
||||
if fc.GetSize() > maxFileSize {
|
||||
return nil, nil, ErrFileTooLarge
|
||||
}
|
||||
return realRepositoryContent{fc}, nil, nil
|
||||
}
|
||||
|
||||
// For directories, check size limits
|
||||
if len(dc) > maxDirectoryItems {
|
||||
return nil, nil, fmt.Errorf("directory contains too many items (more than %d)", maxDirectoryItems)
|
||||
}
|
||||
|
||||
// Convert directory contents
|
||||
allContents := make([]RepositoryContent, 0, len(dc))
|
||||
for _, original := range dc {
|
||||
allContents = append(allContents, realRepositoryContent{original})
|
||||
}
|
||||
|
||||
return nil, allContents, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) GetTree(ctx context.Context, owner, repository, basePath, ref string, recursive bool) ([]RepositoryContent, bool, error) {
|
||||
var tree *github.Tree
|
||||
var err error
|
||||
|
||||
subPaths := safepath.Split(basePath)
|
||||
currentRef := ref
|
||||
|
||||
for {
|
||||
// If subPaths is empty, we can read recursively, as we're reading the tree from the "base" of the repository. Otherwise, always read only the direct children.
|
||||
recursive := recursive && len(subPaths) == 0
|
||||
|
||||
tree, _, err = r.gh.Git.GetTree(ctx, owner, repository, currentRef, recursive)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return nil, false, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return nil, false, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
if currentRef != ref {
|
||||
// We're operating with a subpath which doesn't exist yet.
|
||||
// Pretend as if there is simply no files.
|
||||
return nil, false, nil
|
||||
}
|
||||
// currentRef == ref
|
||||
// This indicates the repository or commitish reference doesn't exist. This should always return an error.
|
||||
return nil, false, ErrResourceNotFound
|
||||
}
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Check if we've exceeded the maximum allowed items
|
||||
if len(tree.Entries) > maxTreeItems {
|
||||
return nil, false, fmt.Errorf("tree contains too many items (more than %d)", maxTreeItems)
|
||||
}
|
||||
|
||||
// Prep for next iteration.
|
||||
if len(subPaths) == 0 {
|
||||
// We're done: we've discovered the tree we want.
|
||||
break
|
||||
}
|
||||
|
||||
// the ref must be equal the SHA of the entry corresponding to subPaths[0]
|
||||
currentRef = ""
|
||||
for _, e := range tree.Entries {
|
||||
if e.GetPath() == subPaths[0] {
|
||||
currentRef = e.GetSHA()
|
||||
break
|
||||
}
|
||||
}
|
||||
subPaths = subPaths[1:]
|
||||
if currentRef == "" {
|
||||
// We couldn't find the folder in the tree...
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// If the tree is truncated and we're in recursive mode, return an error
|
||||
if tree.GetTruncated() && recursive {
|
||||
return nil, true, fmt.Errorf("tree is too large to fetch recursively (more than %d items)", maxTreeItems)
|
||||
}
|
||||
|
||||
entries := make([]RepositoryContent, 0, len(tree.Entries))
|
||||
for _, te := range tree.Entries {
|
||||
rrc := &realRepositoryContent{
|
||||
real: &github.RepositoryContent{
|
||||
Path: te.Path,
|
||||
Size: te.Size,
|
||||
SHA: te.SHA,
|
||||
},
|
||||
}
|
||||
if te.GetType() == "tree" {
|
||||
rrc.real.Type = github.Ptr("dir")
|
||||
} else {
|
||||
rrc.real.Type = te.Type
|
||||
}
|
||||
entries = append(entries, rrc)
|
||||
}
|
||||
return entries, tree.GetTruncated(), nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreateFile(ctx context.Context, owner, repository, path, branch, message string, content []byte) error {
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("Create %s", path)
|
||||
}
|
||||
|
||||
_, _, err := r.gh.Repositories.CreateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
||||
Branch: &branch,
|
||||
Message: &message,
|
||||
Content: content,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusUnprocessableEntity {
|
||||
return ErrResourceAlreadyExists
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) UpdateFile(ctx context.Context, owner, repository, path, branch, message, hash string, content []byte) error {
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("Update %s", path)
|
||||
}
|
||||
|
||||
_, _, err := r.gh.Repositories.UpdateFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
||||
Branch: &branch,
|
||||
Message: &message,
|
||||
Content: content,
|
||||
SHA: &hash,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusConflict {
|
||||
return ErrMismatchedHash
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) DeleteFile(ctx context.Context, owner, repository, path, branch, message, hash string) error {
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("Delete %s", path)
|
||||
}
|
||||
|
||||
_, _, err := r.gh.Repositories.DeleteFile(ctx, owner, repository, path, &github.RepositoryContentFileOptions{
|
||||
Branch: &branch,
|
||||
Message: &message,
|
||||
SHA: &hash,
|
||||
})
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusConflict {
|
||||
return ErrMismatchedHash
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Commits returns a list of commits for a given repository and branch.
|
||||
func (r *githubClient) Commits(ctx context.Context, owner, repository, path, branch string) ([]Commit, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.RepositoryCommit, *github.Response, error) {
|
||||
return r.gh.Repositories.ListCommits(ctx, owner, repository, &github.CommitsListOptions{
|
||||
Path: path,
|
||||
SHA: branch,
|
||||
ListOptions: *opts,
|
||||
})
|
||||
}
|
||||
|
||||
commits, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxCommits),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("too many commits to fetch (more than %d)", maxCommits)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]Commit, 0, len(commits))
|
||||
for _, c := range commits {
|
||||
var createdAt time.Time
|
||||
var author *CommitAuthor
|
||||
if c.GetCommit().GetAuthor() != nil {
|
||||
author = &CommitAuthor{
|
||||
Name: c.GetCommit().GetAuthor().GetName(),
|
||||
Username: c.GetAuthor().GetLogin(),
|
||||
AvatarURL: c.GetAuthor().GetAvatarURL(),
|
||||
}
|
||||
|
||||
createdAt = c.GetCommit().GetAuthor().GetDate().Time
|
||||
}
|
||||
|
||||
var committer *CommitAuthor
|
||||
if c.GetCommitter() != nil {
|
||||
committer = &CommitAuthor{
|
||||
Name: c.GetCommit().GetCommitter().GetName(),
|
||||
Username: c.GetCommitter().GetLogin(),
|
||||
AvatarURL: c.GetCommitter().GetAvatarURL(),
|
||||
}
|
||||
}
|
||||
|
||||
ret = append(ret, Commit{
|
||||
Ref: c.GetSHA(),
|
||||
Message: c.GetCommit().GetMessage(),
|
||||
Author: author,
|
||||
Committer: committer,
|
||||
CreatedAt: createdAt,
|
||||
})
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CompareCommits(ctx context.Context, owner, repository, base, head string) ([]CommitFile, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) {
|
||||
compare, resp, err := r.gh.Repositories.CompareCommits(ctx, owner, repository, base, head, opts)
|
||||
if err != nil {
|
||||
return nil, resp, err
|
||||
}
|
||||
return compare.Files, resp, nil
|
||||
}
|
||||
|
||||
files, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxCompareFiles),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("too many files changed between commits (more than %d)", maxCompareFiles)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to the interface type
|
||||
ret := make([]CommitFile, 0, len(files))
|
||||
for _, f := range files {
|
||||
ret = append(ret, f)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) GetBranch(ctx context.Context, owner, repository, branchName string) (Branch, error) {
|
||||
branch, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return Branch{}, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return Branch{}, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return Branch{}, ErrResourceNotFound
|
||||
}
|
||||
return Branch{}, err
|
||||
}
|
||||
|
||||
return Branch{
|
||||
Name: branch.GetName(),
|
||||
Sha: branch.GetCommit().GetSHA(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreateBranch(ctx context.Context, owner, repository, sourceBranch, branchName string) error {
|
||||
// Fail if the branch already exists
|
||||
if _, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0); err == nil {
|
||||
return ErrResourceAlreadyExists
|
||||
}
|
||||
|
||||
// Branch out based on the repository branch
|
||||
baseRef, _, err := r.gh.Repositories.GetBranch(ctx, owner, repository, sourceBranch, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get base branch: %w", err)
|
||||
}
|
||||
|
||||
if _, _, err := r.gh.Git.CreateRef(ctx, owner, repository, &github.Reference{
|
||||
Ref: github.Ptr(fmt.Sprintf("refs/heads/%s", branchName)),
|
||||
Object: &github.GitObject{
|
||||
SHA: baseRef.Commit.SHA,
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("create branch ref: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubClient) BranchExists(ctx context.Context, owner, repository, branchName string) (bool, error) {
|
||||
_, resp, err := r.gh.Repositories.GetBranch(ctx, owner, repository, branchName, 0)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
func (r *githubClient) ListWebhooks(ctx context.Context, owner, repository string) ([]WebhookConfig, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.Hook, *github.Response, error) {
|
||||
return r.gh.Repositories.ListHooks(ctx, owner, repository, opts)
|
||||
}
|
||||
|
||||
hooks, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxWebhooks),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("too many webhooks configured (more than %d)", maxWebhooks)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-allocate the result slice
|
||||
ret := make([]WebhookConfig, 0, len(hooks))
|
||||
for _, h := range hooks {
|
||||
contentType := h.GetConfig().GetContentType()
|
||||
if contentType == "" {
|
||||
contentType = "form"
|
||||
}
|
||||
|
||||
ret = append(ret, WebhookConfig{
|
||||
ID: h.GetID(),
|
||||
Events: h.Events,
|
||||
Active: h.GetActive(),
|
||||
URL: h.GetConfig().GetURL(),
|
||||
ContentType: contentType,
|
||||
// Intentionally not setting Secret.
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreateWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) (WebhookConfig, error) {
|
||||
if cfg.ContentType == "" {
|
||||
cfg.ContentType = "form"
|
||||
}
|
||||
|
||||
hook := &github.Hook{
|
||||
URL: &cfg.URL,
|
||||
Events: cfg.Events,
|
||||
Active: &cfg.Active,
|
||||
Config: &github.HookConfig{
|
||||
ContentType: &cfg.ContentType,
|
||||
Secret: &cfg.Secret,
|
||||
URL: &cfg.URL,
|
||||
},
|
||||
}
|
||||
|
||||
createdHook, _, err := r.gh.Repositories.CreateHook(ctx, owner, repository, hook)
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return WebhookConfig{}, ErrServiceUnavailable
|
||||
}
|
||||
if err != nil {
|
||||
return WebhookConfig{}, err
|
||||
}
|
||||
|
||||
return WebhookConfig{
|
||||
ID: createdHook.GetID(),
|
||||
// events is not returned by GitHub.
|
||||
Events: cfg.Events,
|
||||
Active: createdHook.GetActive(),
|
||||
URL: createdHook.GetConfig().GetURL(),
|
||||
ContentType: createdHook.GetConfig().GetContentType(),
|
||||
// Secret is not returned by GitHub.
|
||||
Secret: cfg.Secret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) GetWebhook(ctx context.Context, owner, repository string, webhookID int64) (WebhookConfig, error) {
|
||||
hook, _, err := r.gh.Repositories.GetHook(ctx, owner, repository, webhookID)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return WebhookConfig{}, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return WebhookConfig{}, ErrResourceNotFound
|
||||
}
|
||||
return WebhookConfig{}, err
|
||||
}
|
||||
|
||||
contentType := hook.GetConfig().GetContentType()
|
||||
if contentType == "" {
|
||||
contentType = "json"
|
||||
}
|
||||
|
||||
return WebhookConfig{
|
||||
ID: hook.GetID(),
|
||||
Events: hook.Events,
|
||||
Active: hook.GetActive(),
|
||||
URL: hook.GetConfig().GetURL(),
|
||||
ContentType: contentType,
|
||||
// Intentionally not setting Secret.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) DeleteWebhook(ctx context.Context, owner, repository string, webhookID int64) error {
|
||||
_, err := r.gh.Repositories.DeleteHook(ctx, owner, repository, webhookID)
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) EditWebhook(ctx context.Context, owner, repository string, cfg WebhookConfig) error {
|
||||
if cfg.ContentType == "" {
|
||||
cfg.ContentType = "form"
|
||||
}
|
||||
|
||||
hook := &github.Hook{
|
||||
URL: &cfg.URL,
|
||||
Events: cfg.Events,
|
||||
Active: &cfg.Active,
|
||||
Config: &github.HookConfig{
|
||||
ContentType: &cfg.ContentType,
|
||||
Secret: &cfg.Secret,
|
||||
URL: &cfg.URL,
|
||||
},
|
||||
}
|
||||
_, _, err := r.gh.Repositories.EditHook(ctx, owner, repository, cfg.ID, hook)
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *githubClient) ListPullRequestFiles(ctx context.Context, owner, repository string, number int) ([]CommitFile, error) {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.CommitFile, *github.Response, error) {
|
||||
return r.gh.PullRequests.ListFiles(ctx, owner, repository, number, opts)
|
||||
}
|
||||
|
||||
files, err := paginatedList(
|
||||
ctx,
|
||||
listFn,
|
||||
defaultListOptions(maxPRFiles),
|
||||
)
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return nil, fmt.Errorf("pull request contains too many files (more than %d)", maxPRFiles)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert to the interface type
|
||||
ret := make([]CommitFile, 0, len(files))
|
||||
for _, f := range files {
|
||||
ret = append(ret, f)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreatePullRequestComment(ctx context.Context, owner, repository string, number int, body string) error {
|
||||
comment := &github.IssueComment{
|
||||
Body: &body,
|
||||
}
|
||||
|
||||
if _, _, err := r.gh.Issues.CreateComment(ctx, owner, repository, number, comment); err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubClient) CreatePullRequestFileComment(ctx context.Context, owner, repository string, number int, comment FileComment) error {
|
||||
commentRequest := &github.PullRequestComment{
|
||||
Body: &comment.Content,
|
||||
CommitID: &comment.Ref,
|
||||
Path: &comment.Path,
|
||||
Position: &comment.Position,
|
||||
}
|
||||
|
||||
if _, _, err := r.gh.PullRequests.CreateComment(ctx, owner, repository, number, commentRequest); err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return ErrServiceUnavailable
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *githubClient) ClearAllPullRequestFileComments(ctx context.Context, owner, repository string, number int) error {
|
||||
listFn := func(ctx context.Context, opts *github.ListOptions) ([]*github.PullRequestComment, *github.Response, error) {
|
||||
return r.gh.PullRequests.ListComments(ctx, owner, repository, number, &github.PullRequestListCommentsOptions{
|
||||
ListOptions: *opts,
|
||||
})
|
||||
}
|
||||
|
||||
comments, err := paginatedList(ctx, listFn, defaultListOptions(maxPullRequestsFileComments))
|
||||
if errors.Is(err, ErrTooManyItems) {
|
||||
return fmt.Errorf("too many comments to process (more than %d)", maxPullRequestsFileComments)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userLogin, _, err := r.gh.Users.Get(ctx, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("get user: %w", err)
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
// skip if comments were not created by us
|
||||
if c.User.GetLogin() != userLogin.GetLogin() {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := r.gh.PullRequests.DeleteComment(ctx, owner, repository, c.GetID()); err != nil {
|
||||
return fmt.Errorf("delete comment: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type realRepositoryContent struct {
|
||||
real *github.RepositoryContent
|
||||
}
|
||||
|
||||
var _ RepositoryContent = realRepositoryContent{}
|
||||
|
||||
func (c realRepositoryContent) IsDirectory() bool {
|
||||
return c.real.GetType() == "dir"
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetFileContent() (string, error) {
|
||||
return c.real.GetContent()
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) IsSymlink() bool {
|
||||
return c.real.Target != nil
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetPath() string {
|
||||
return c.real.GetPath()
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetSHA() string {
|
||||
return c.real.GetSHA()
|
||||
}
|
||||
|
||||
func (c realRepositoryContent) GetSize() int64 {
|
||||
if c.real.Size != nil {
|
||||
return int64(*c.real.Size)
|
||||
}
|
||||
if c.real.Content != nil {
|
||||
if c, err := c.real.GetContent(); err == nil {
|
||||
return int64(len(c))
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// listOptions represents pagination parameters for list operations
|
||||
type listOptions struct {
|
||||
github.ListOptions
|
||||
MaxItems int
|
||||
}
|
||||
|
||||
// defaultListOptions returns a ListOptions with sensible defaults
|
||||
func defaultListOptions(maxItems int) listOptions {
|
||||
return listOptions{
|
||||
ListOptions: github.ListOptions{
|
||||
Page: 1,
|
||||
PerPage: 100,
|
||||
},
|
||||
MaxItems: maxItems,
|
||||
}
|
||||
}
|
||||
|
||||
// paginatedList is a generic function to handle GitHub API pagination
|
||||
func paginatedList[T any](
|
||||
ctx context.Context,
|
||||
listFn func(context.Context, *github.ListOptions) ([]T, *github.Response, error),
|
||||
opts listOptions,
|
||||
) ([]T, error) {
|
||||
var allItems []T
|
||||
|
||||
for {
|
||||
items, resp, err := listFn(ctx, &opts.ListOptions)
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if !errors.As(err, &ghErr) {
|
||||
return nil, err
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return nil, ErrServiceUnavailable
|
||||
}
|
||||
if ghErr.Response.StatusCode == http.StatusNotFound {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pre-allocate the slice if this is the first page
|
||||
if allItems == nil {
|
||||
allItems = make([]T, 0, len(items)*2) // Estimate double the first page size
|
||||
}
|
||||
|
||||
allItems = append(allItems, items...)
|
||||
|
||||
// Check if we've exceeded the maximum allowed items
|
||||
if len(allItems) > opts.MaxItems {
|
||||
return nil, ErrTooManyItems
|
||||
}
|
||||
|
||||
// If there are no more pages, break
|
||||
if resp.NextPage == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Set up next page
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
|
||||
return allItems, nil
|
||||
}
|
||||
Vendored
+231
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"action": "created",
|
||||
"issue": {
|
||||
"id": 2726065547,
|
||||
"number": 12,
|
||||
"state": "open",
|
||||
"locked": false,
|
||||
"title": "Webhook test PR",
|
||||
"author_association": "MEMBER",
|
||||
"user": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"comments": 1,
|
||||
"created_at": "2024-12-09T05:53:14Z",
|
||||
"updated_at": "2024-12-09T06:03:21Z",
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/comments",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/events",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/labels{/name}",
|
||||
"repository_url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
|
||||
"pull_request": {
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12",
|
||||
"diff_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.diff",
|
||||
"patch_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.patch"
|
||||
},
|
||||
"reactions": {
|
||||
"total_count": 0,
|
||||
"+1": 0,
|
||||
"-1": 0,
|
||||
"laugh": 0,
|
||||
"confused": 0,
|
||||
"heart": 0,
|
||||
"hooray": 0,
|
||||
"rocket": 0,
|
||||
"eyes": 0,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/reactions"
|
||||
},
|
||||
"node_id": "PR_kwDONO4cS86EfDVR",
|
||||
"draft": false
|
||||
},
|
||||
"comment": {
|
||||
"id": 2527008082,
|
||||
"node_id": "IC_kwDONO4cS86WnxVS",
|
||||
"body": "comment in PR",
|
||||
"user": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"reactions": {
|
||||
"total_count": 0,
|
||||
"+1": 0,
|
||||
"-1": 0,
|
||||
"laugh": 0,
|
||||
"confused": 0,
|
||||
"heart": 0,
|
||||
"hooray": 0,
|
||||
"rocket": 0,
|
||||
"eyes": 0,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments/2527008082/reactions"
|
||||
},
|
||||
"created_at": "2024-12-09T06:03:19Z",
|
||||
"updated_at": "2024-12-09T06:03:19Z",
|
||||
"author_association": "MEMBER",
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments/2527008082",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12#issuecomment-2527008082",
|
||||
"issue_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12"
|
||||
},
|
||||
"repository": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"default_branch": "main",
|
||||
"created_at": "2024-11-13T17:13:33Z",
|
||||
"pushed_at": "2024-12-09T05:58:00Z",
|
||||
"updated_at": "2024-12-09T05:58:03Z",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 9,
|
||||
"open_issues": 9,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"watchers": 0,
|
||||
"size": 141,
|
||||
"allow_forking": false,
|
||||
"web_commit_signoff_required": false,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"private": true,
|
||||
"has_issues": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_projects": true,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"is_template": false,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
|
||||
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
|
||||
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
|
||||
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
|
||||
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
|
||||
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
|
||||
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
|
||||
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
|
||||
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
|
||||
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
|
||||
"visibility": "internal"
|
||||
},
|
||||
"sender": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"organization": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
|
||||
"url": "https://api.github.com/orgs/grafana",
|
||||
"events_url": "https://api.github.com/orgs/grafana/events",
|
||||
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
|
||||
"issues_url": "https://api.github.com/orgs/grafana/issues",
|
||||
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
|
||||
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
|
||||
"repos_url": "https://api.github.com/orgs/grafana/repos"
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"zen": "Keep it logically awesome.",
|
||||
"hook_id": 517704995,
|
||||
"hook": {
|
||||
"created_at": "2024-12-09T05:44:20Z",
|
||||
"updated_at": "2024-12-09T05:44:20Z",
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks/517704995",
|
||||
"id": 517704995,
|
||||
"type": "Repository",
|
||||
"name": "web",
|
||||
"test_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks/517704995/test",
|
||||
"ping_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks/517704995/pings",
|
||||
"last_response": {
|
||||
"code": null,
|
||||
"message": null,
|
||||
"status": "unused"
|
||||
},
|
||||
"config": {
|
||||
"content_type": "form",
|
||||
"insecure_ssl": "0",
|
||||
"url": "https://0b71-216-128-0-90.ngrok-free.app/apis/provisioning.grafana.app/v0alpha1/namespaces/default/repositories/github-example-ryan/webhook",
|
||||
"secret": "********"
|
||||
},
|
||||
"events": [
|
||||
"*"
|
||||
],
|
||||
"active": true
|
||||
},
|
||||
"repository": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"default_branch": "main",
|
||||
"created_at": "2024-11-13T17:13:33Z",
|
||||
"pushed_at": "2024-12-08T10:51:24Z",
|
||||
"updated_at": "2024-11-28T12:53:26Z",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 8,
|
||||
"open_issues": 8,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"watchers": 0,
|
||||
"size": 141,
|
||||
"allow_forking": false,
|
||||
"web_commit_signoff_required": false,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"private": true,
|
||||
"has_issues": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_projects": true,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"is_template": false,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
|
||||
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
|
||||
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
|
||||
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
|
||||
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
|
||||
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
|
||||
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
|
||||
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
|
||||
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
|
||||
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
|
||||
"visibility": "internal"
|
||||
},
|
||||
"sender": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
}
|
||||
}
|
||||
+470
@@ -0,0 +1,470 @@
|
||||
|
||||
{
|
||||
"action": "opened",
|
||||
"number": 12,
|
||||
"pull_request": {
|
||||
"id": 2222732625,
|
||||
"number": 12,
|
||||
"state": "open",
|
||||
"locked": false,
|
||||
"title": "Webhook test PR",
|
||||
"created_at": "2024-12-09T05:53:14Z",
|
||||
"updated_at": "2024-12-09T05:53:14Z",
|
||||
"user": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"draft": false,
|
||||
"merged": false,
|
||||
"mergeable_state": "unknown",
|
||||
"comments": 0,
|
||||
"commits": 1,
|
||||
"additions": 1,
|
||||
"deletions": 0,
|
||||
"changed_files": 1,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo/pull/12",
|
||||
"issue_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/ab5446a53df9e5f8bdeed52250f51fad08e822bc",
|
||||
"diff_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.diff",
|
||||
"patch_url": "https://github.com/grafana/git-ui-sync-demo/pull/12.patch",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/commits",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/comments",
|
||||
"review_comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/comments{/number}",
|
||||
"review_comments": 0,
|
||||
"maintainer_can_modify": false,
|
||||
"author_association": "MEMBER",
|
||||
"node_id": "PR_kwDONO4cS86EfDVR",
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/grafana/git-ui-sync-demo/pull/12"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/12/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls/12/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/ab5446a53df9e5f8bdeed52250f51fad08e822bc"
|
||||
}
|
||||
},
|
||||
"head": {
|
||||
"label": "grafana:dashboard/1733653266690",
|
||||
"ref": "dashboard/1733653266690",
|
||||
"sha": "ab5446a53df9e5f8bdeed52250f51fad08e822bc",
|
||||
"repo": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"default_branch": "main",
|
||||
"created_at": "2024-11-13T17:13:33Z",
|
||||
"pushed_at": "2024-12-08T10:51:24Z",
|
||||
"updated_at": "2024-11-28T12:53:26Z",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 9,
|
||||
"open_issues": 9,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"watchers": 0,
|
||||
"size": 141,
|
||||
"allow_rebase_merge": true,
|
||||
"allow_update_branch": false,
|
||||
"allow_squash_merge": true,
|
||||
"allow_merge_commit": true,
|
||||
"allow_auto_merge": false,
|
||||
"allow_forking": false,
|
||||
"web_commit_signoff_required": false,
|
||||
"delete_branch_on_merge": false,
|
||||
"use_squash_pr_title_as_default": false,
|
||||
"squash_merge_commit_title": "COMMIT_OR_PR_TITLE",
|
||||
"squash_merge_commit_message": "COMMIT_MESSAGES",
|
||||
"merge_commit_title": "MERGE_MESSAGE",
|
||||
"merge_commit_message": "PR_TITLE",
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"private": true,
|
||||
"has_issues": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_projects": true,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"is_template": false,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
|
||||
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
|
||||
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
|
||||
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
|
||||
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
|
||||
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
|
||||
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
|
||||
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
|
||||
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
|
||||
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
|
||||
"visibility": "internal"
|
||||
},
|
||||
"user": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "grafana:main",
|
||||
"ref": "main",
|
||||
"sha": "6c86a0cdfd220c2fe3518cfaa4a4babf030d9a7a",
|
||||
"repo": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"default_branch": "main",
|
||||
"created_at": "2024-11-13T17:13:33Z",
|
||||
"pushed_at": "2024-12-08T10:51:24Z",
|
||||
"updated_at": "2024-11-28T12:53:26Z",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 9,
|
||||
"open_issues": 9,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"watchers": 0,
|
||||
"size": 141,
|
||||
"allow_rebase_merge": true,
|
||||
"allow_update_branch": false,
|
||||
"allow_squash_merge": true,
|
||||
"allow_merge_commit": true,
|
||||
"allow_auto_merge": false,
|
||||
"allow_forking": false,
|
||||
"web_commit_signoff_required": false,
|
||||
"delete_branch_on_merge": false,
|
||||
"use_squash_pr_title_as_default": false,
|
||||
"squash_merge_commit_title": "COMMIT_OR_PR_TITLE",
|
||||
"squash_merge_commit_message": "COMMIT_MESSAGES",
|
||||
"merge_commit_title": "MERGE_MESSAGE",
|
||||
"merge_commit_message": "PR_TITLE",
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"private": true,
|
||||
"has_issues": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_projects": true,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"is_template": false,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
|
||||
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
|
||||
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
|
||||
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
|
||||
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
|
||||
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
|
||||
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
|
||||
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
|
||||
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
|
||||
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
|
||||
"visibility": "internal"
|
||||
},
|
||||
"user": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"default_branch": "main",
|
||||
"created_at": "2024-11-13T17:13:33Z",
|
||||
"pushed_at": "2024-12-08T10:51:24Z",
|
||||
"updated_at": "2024-11-28T12:53:26Z",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"fork": false,
|
||||
"forks_count": 0,
|
||||
"open_issues_count": 9,
|
||||
"open_issues": 9,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"watchers": 0,
|
||||
"size": 141,
|
||||
"allow_forking": false,
|
||||
"web_commit_signoff_required": false,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"private": true,
|
||||
"has_issues": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_projects": true,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"is_template": false,
|
||||
"url": "https://api.github.com/repos/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
|
||||
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
|
||||
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
|
||||
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
|
||||
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
|
||||
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
|
||||
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
|
||||
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
|
||||
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
|
||||
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
|
||||
"visibility": "internal"
|
||||
},
|
||||
"sender": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"organization": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
|
||||
"url": "https://api.github.com/orgs/grafana",
|
||||
"events_url": "https://api.github.com/orgs/grafana/events",
|
||||
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
|
||||
"issues_url": "https://api.github.com/orgs/grafana/issues",
|
||||
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
|
||||
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
|
||||
"repos_url": "https://api.github.com/orgs/grafana/repos"
|
||||
}
|
||||
}
|
||||
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"ref": "refs/heads/not-main",
|
||||
"commits": [
|
||||
{
|
||||
"message": "Update README.md\n\ntest message",
|
||||
"author": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"distinct": true,
|
||||
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
|
||||
"timestamp": "2024-12-09T08:58:00+03:00",
|
||||
"committer": {
|
||||
"name": "GitHub",
|
||||
"email": "noreply@github.com",
|
||||
"username": "web-flow"
|
||||
},
|
||||
"modified": [
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
],
|
||||
"before": "6c86a0cdfd220c2fe3518cfaa4a4babf030d9a7a",
|
||||
"after": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"created": false,
|
||||
"deleted": false,
|
||||
"forced": false,
|
||||
"compare": "https://github.com/grafana/git-ui-sync-demo/compare/6c86a0cdfd22...72096e3adc64",
|
||||
"repository": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"name": "grafana",
|
||||
"email": "hello@grafana.com",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"private": true,
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"fork": false,
|
||||
"created_at": "2024-11-13T20:13:33+03:00",
|
||||
"pushed_at": "2024-12-09T08:58:00+03:00",
|
||||
"updated_at": "2024-11-28T12:53:26Z",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"size": 141,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"forks_count": 0,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"open_issues_count": 9,
|
||||
"default_branch": "main",
|
||||
"master_branch": "main",
|
||||
"organization": "grafana",
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo"
|
||||
},
|
||||
"head_commit": {
|
||||
"message": "Update README.md\n\ntest message",
|
||||
"author": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"distinct": true,
|
||||
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
|
||||
"timestamp": "2024-12-09T08:58:00+03:00",
|
||||
"committer": {
|
||||
"name": "GitHub",
|
||||
"email": "noreply@github.com",
|
||||
"username": "web-flow"
|
||||
},
|
||||
"modified": [
|
||||
"README.md"
|
||||
]
|
||||
},
|
||||
"pusher": {
|
||||
"name": "ryantxu",
|
||||
"email": "ryantxu@gmail.com"
|
||||
},
|
||||
"sender": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"organization": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
|
||||
"url": "https://api.github.com/orgs/grafana",
|
||||
"events_url": "https://api.github.com/orgs/grafana/events",
|
||||
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
|
||||
"issues_url": "https://api.github.com/orgs/grafana/issues",
|
||||
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
|
||||
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
|
||||
"repos_url": "https://api.github.com/orgs/grafana/repos"
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
{
|
||||
"ref": "refs/heads/main",
|
||||
"before": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"after": "5c816f9812e391c62b0c5555d0b473b296d9179c",
|
||||
"repository": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"private": true,
|
||||
"owner": {
|
||||
"name": "grafana",
|
||||
"email": "hello@grafana.com",
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"type": "Organization",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"fork": false,
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"forks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/forks",
|
||||
"keys_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/teams",
|
||||
"hooks_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/events",
|
||||
"assignees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/tags",
|
||||
"blobs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/subscription",
|
||||
"commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/merges",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/downloads",
|
||||
"issues_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/deployments",
|
||||
"created_at": 1731518013,
|
||||
"updated_at": "2024-12-09T05:58:03Z",
|
||||
"pushed_at": 1733731254,
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"homepage": null,
|
||||
"size": 142,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"language": null,
|
||||
"has_issues": true,
|
||||
"has_projects": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_discussions": false,
|
||||
"forks_count": 0,
|
||||
"mirror_url": null,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"open_issues_count": 9,
|
||||
"license": null,
|
||||
"allow_forking": false,
|
||||
"is_template": false,
|
||||
"web_commit_signoff_required": false,
|
||||
"topics": [
|
||||
|
||||
],
|
||||
"visibility": "internal",
|
||||
"forks": 0,
|
||||
"open_issues": 9,
|
||||
"watchers": 0,
|
||||
"default_branch": "main",
|
||||
"stargazers": 0,
|
||||
"master_branch": "main",
|
||||
"organization": "grafana",
|
||||
"custom_properties": {
|
||||
|
||||
}
|
||||
},
|
||||
"pusher": {
|
||||
"name": "ryantxu",
|
||||
"email": "ryantxu@gmail.com"
|
||||
},
|
||||
"organization": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"url": "https://api.github.com/orgs/grafana",
|
||||
"repos_url": "https://api.github.com/orgs/grafana/repos",
|
||||
"events_url": "https://api.github.com/orgs/grafana/events",
|
||||
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
|
||||
"issues_url": "https://api.github.com/orgs/grafana/issues",
|
||||
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
|
||||
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open & composable observability platform."
|
||||
},
|
||||
"enterprise": {
|
||||
"id": 129980,
|
||||
"slug": "grafana",
|
||||
"name": "Grafana Labs Enterprise",
|
||||
"node_id": "E_kgDOAAH7vA",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/b/129980?v=4",
|
||||
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open & composable observability platform.",
|
||||
"website_url": "https://grafana.com",
|
||||
"html_url": "https://github.com/enterprises/grafana",
|
||||
"created_at": "2024-02-29T23:01:47Z",
|
||||
"updated_at": "2024-10-21T08:45:28Z"
|
||||
},
|
||||
"sender": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"type": "User",
|
||||
"user_view_type": "public",
|
||||
"site_admin": false
|
||||
},
|
||||
"created": false,
|
||||
"deleted": false,
|
||||
"forced": false,
|
||||
"base_ref": null,
|
||||
"compare": "https://github.com/grafana/git-ui-sync-demo/compare/72096e3adc64...5c816f9812e3",
|
||||
"commits": [
|
||||
{
|
||||
"id": "5c816f9812e391c62b0c5555d0b473b296d9179c",
|
||||
"tree_id": "97c7ba756b07b6a2b7fd130e59f75deed53fe027",
|
||||
"distinct": true,
|
||||
"message": "nested folders",
|
||||
"timestamp": "2024-12-09T11:00:48+03:00",
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo/commit/5c816f9812e391c62b0c5555d0b473b296d9179c",
|
||||
"author": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"committer": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"added": [
|
||||
"nested-1/README.md",
|
||||
"nested-1/dash-1.json",
|
||||
"nested-1/nested-2/README.md",
|
||||
"nested-1/nested-2/dash-2.json"
|
||||
],
|
||||
"removed": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"first-dashboard.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"head_commit": {
|
||||
"id": "5c816f9812e391c62b0c5555d0b473b296d9179c",
|
||||
"tree_id": "97c7ba756b07b6a2b7fd130e59f75deed53fe027",
|
||||
"distinct": true,
|
||||
"message": "nested folders",
|
||||
"timestamp": "2024-12-09T11:00:48+03:00",
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo/commit/5c816f9812e391c62b0c5555d0b473b296d9179c",
|
||||
"author": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"committer": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"added": [
|
||||
"nested-1/README.md",
|
||||
"nested-1/dash-1.json",
|
||||
"nested-1/nested-2/README.md",
|
||||
"nested-1/nested-2/dash-2.json"
|
||||
],
|
||||
"removed": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"first-dashboard.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+148
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"ref": "refs/heads/main",
|
||||
"commits": [
|
||||
{
|
||||
"message": "Update README.md\n\ntest message",
|
||||
"author": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"distinct": true,
|
||||
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
|
||||
"timestamp": "2024-12-09T08:58:00+03:00",
|
||||
"committer": {
|
||||
"name": "GitHub",
|
||||
"email": "noreply@github.com",
|
||||
"username": "web-flow"
|
||||
},
|
||||
"modified": [
|
||||
"README.md"
|
||||
]
|
||||
}
|
||||
],
|
||||
"before": "6c86a0cdfd220c2fe3518cfaa4a4babf030d9a7a",
|
||||
"after": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"created": false,
|
||||
"deleted": false,
|
||||
"forced": false,
|
||||
"compare": "https://github.com/grafana/git-ui-sync-demo/compare/6c86a0cdfd22...72096e3adc64",
|
||||
"repository": {
|
||||
"id": 888020043,
|
||||
"node_id": "R_kgDONO4cSw",
|
||||
"name": "git-ui-sync-demo",
|
||||
"full_name": "grafana/git-ui-sync-demo",
|
||||
"owner": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"html_url": "https://github.com/grafana",
|
||||
"gravatar_id": "",
|
||||
"name": "grafana",
|
||||
"email": "hello@grafana.com",
|
||||
"type": "Organization",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/grafana",
|
||||
"events_url": "https://api.github.com/users/grafana/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/grafana/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/grafana/followers",
|
||||
"gists_url": "https://api.github.com/users/grafana/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/grafana/orgs",
|
||||
"received_events_url": "https://api.github.com/users/grafana/received_events",
|
||||
"repos_url": "https://api.github.com/users/grafana/repos",
|
||||
"starred_url": "https://api.github.com/users/grafana/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/grafana/subscriptions"
|
||||
},
|
||||
"private": true,
|
||||
"description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.",
|
||||
"fork": false,
|
||||
"created_at": "2024-11-13T20:13:33+03:00",
|
||||
"pushed_at": "2024-12-09T08:58:00+03:00",
|
||||
"updated_at": "2024-11-28T12:53:26Z",
|
||||
"pulls_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/pulls{/number}",
|
||||
"size": 141,
|
||||
"stargazers_count": 0,
|
||||
"watchers_count": 0,
|
||||
"has_issues": true,
|
||||
"has_downloads": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"forks_count": 0,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"open_issues_count": 9,
|
||||
"default_branch": "main",
|
||||
"master_branch": "main",
|
||||
"organization": "grafana",
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"archive_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/{archive_format}{/ref}",
|
||||
"html_url": "https://github.com/grafana/git-ui-sync-demo",
|
||||
"statuses_url": "https://api.github.com/repos/grafana/git-ui-sync-demo/statuses/{sha}",
|
||||
"git_url": "git://github.com/grafana/git-ui-sync-demo.git",
|
||||
"ssh_url": "git@github.com:grafana/git-ui-sync-demo.git",
|
||||
"clone_url": "https://github.com/grafana/git-ui-sync-demo.git",
|
||||
"svn_url": "https://github.com/grafana/git-ui-sync-demo"
|
||||
},
|
||||
"head_commit": {
|
||||
"message": "Update README.md\n\ntest message",
|
||||
"author": {
|
||||
"name": "Ryan McKinley",
|
||||
"email": "ryantxu@gmail.com",
|
||||
"username": "ryantxu"
|
||||
},
|
||||
"url": "https://github.com/grafana/git-ui-sync-demo/commit/72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"distinct": true,
|
||||
"id": "72096e3adc646c5a5b8a91744f962b12bac06045",
|
||||
"tree_id": "03ff034c54bcefae2f96041f3fb8172f2fe93df3",
|
||||
"timestamp": "2024-12-09T08:58:00+03:00",
|
||||
"committer": {
|
||||
"name": "GitHub",
|
||||
"email": "noreply@github.com",
|
||||
"username": "web-flow"
|
||||
},
|
||||
"modified": [
|
||||
"README.md"
|
||||
]
|
||||
},
|
||||
"pusher": {
|
||||
"name": "ryantxu",
|
||||
"email": "ryantxu@gmail.com"
|
||||
},
|
||||
"sender": {
|
||||
"login": "ryantxu",
|
||||
"id": 705951,
|
||||
"node_id": "MDQ6VXNlcjcwNTk1MQ==",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/705951?v=4",
|
||||
"html_url": "https://github.com/ryantxu",
|
||||
"gravatar_id": "",
|
||||
"type": "User",
|
||||
"site_admin": false,
|
||||
"url": "https://api.github.com/users/ryantxu",
|
||||
"events_url": "https://api.github.com/users/ryantxu/events{/privacy}",
|
||||
"following_url": "https://api.github.com/users/ryantxu/following{/other_user}",
|
||||
"followers_url": "https://api.github.com/users/ryantxu/followers",
|
||||
"gists_url": "https://api.github.com/users/ryantxu/gists{/gist_id}",
|
||||
"organizations_url": "https://api.github.com/users/ryantxu/orgs",
|
||||
"received_events_url": "https://api.github.com/users/ryantxu/received_events",
|
||||
"repos_url": "https://api.github.com/users/ryantxu/repos",
|
||||
"starred_url": "https://api.github.com/users/ryantxu/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/ryantxu/subscriptions"
|
||||
},
|
||||
"organization": {
|
||||
"login": "grafana",
|
||||
"id": 7195757,
|
||||
"node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4",
|
||||
"description": "Grafana Labs is behind leading open source projects Grafana and Loki, and the creator of the first open \u0026 composable observability platform.",
|
||||
"url": "https://api.github.com/orgs/grafana",
|
||||
"events_url": "https://api.github.com/orgs/grafana/events",
|
||||
"hooks_url": "https://api.github.com/orgs/grafana/hooks",
|
||||
"issues_url": "https://api.github.com/orgs/grafana/issues",
|
||||
"members_url": "https://api.github.com/orgs/grafana/members{/member}",
|
||||
"public_members_url": "https://api.github.com/orgs/grafana/public_members{/member}",
|
||||
"repos_url": "https://api.github.com/orgs/grafana/repos"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user