Giter Club home page Giter Club logo

go-git-providers's Introduction

go-git-providers

godev build Go Report Card LICENSE Release

go-git-providers is a general-purpose Go client for interacting with Git providers' APIs.

Supported providers:

  • GitHub API (GitHub.com and on-prem)
  • GitLab API (GitLab.com and on-prem)
  • Bitbucket Server API (on-prem)

Features

  • Consistency: Using the same Client interface and high-level structs for multiple backends.
  • Authentication: Personal Access Tokens/OAuth2 Tokens, and unauthenticated.
  • Pagination: List calls automatically return all available pages.
  • Conditional Requests: Asks the Git provider if cached data is up-to-date before requesting, to avoid being rate limited.
  • Reconciling: Support reconciling desired state towards actual state and drift detection.
  • Low-level access: Access the underlying, provider-specific data easily, if needed, and support applying it to the server.
  • Wrapped errors: Data-rich, Go 1.14-errors are consistent across provider, including cases like rate limit, validation, not found, etc.
  • Go modules: The major version is bumped if breaking changes, or major library upgrades are made.
  • Validation-first: Both server and user data is validated prior to manipulation.
  • URL Parsing: HTTPS user, organization and repository URLs can be parsed into machine-readable structs.
  • Enums: Consistent enums are used across providers for similar lists of values.
  • Domain customization: The user can specify their desired domain for the Git provider backend.
  • Context-first: context.Context is the first parameter for every API call.

Operations and Design

The top-level gitprovider.Client has the following sub-clients with their described capabilities:

  • OrganizationsClient operates on organizations the user has access to.

    • Get a specific organization the user has access to.
    • List all top-level organizations the specific user has access to.
    • Children returns the immediate child-organizations for the specific OrganizationRef.
  • {Org,User}RepositoriesClient operates on repositories for organizations and users, respectively.

    • Get returns the repository for the given reference.
    • List all repositories in the given organization or user account.
    • Create creates a repository, with the specified data and options.
    • Reconcile makes sure the given desired state becomes the actual state in the backing Git provider.

The sub-clients above return gitprovider.Organization or gitprovider.{Org,User}Repository interfaces. These object interfaces lets you access their data (through their .Get() function), internal, provider-specific representation (through their .APIObject() function), or sub-resources like deploy keys and teams.

The following object-scoped clients are available:

  • Organization represents an organization in a Git provider.

    • Teams gives access to the TeamsClient for this specific organization.
      • Get a team within the specific organization.
      • List all teams within the specific organization.
  • UserRepository describes a repository owned by an user.

    • DeployKeys gives access to manipulating deploy keys, using this DeployKeyClient.
      • Get a DeployKey by its name.
      • List all deploy keys for the given repository.
      • Create a deploy key with the given specifications.
      • Reconcile makes sure the given desired state becomes the actual state in the backing Git provider.
  • OrgRepository is a superset of UserRepository, and describes a repository owned by an organization.

    • DeployKeys as in UserRepository.
    • TeamAccess returns a TeamsAccessClient for operating on teams' access to this specific repository.
      • Get a team's permission level of this given repository.
      • List the team access control list for this repository.
      • Create adds a given team to the repository's team access control list.
      • Reconcile makes sure the given desired state (req) becomes the actual state in the backing Git provider.

Wait, how do I Delete or Update an object?

That's done on the returned objects themselves, using the following Updatable, Reconcilable and Deletable interfaces implemented by {Org,User}Repository, DeployKey and TeamAccess:

// Updatable is an interface which all objects that can be updated
// using the Client implement.
type Updatable interface {
    // Update will apply the desired state in this object to the server.
    // Only set fields will be respected (i.e. PATCH behaviour).
    // In order to apply changes to this object, use the .Set({Resource}Info) error
    // function, or cast .APIObject() to a pointer to the provider-specific type
    // and set custom fields there.
    //
    // ErrNotFound is returned if the resource does not exist.
    //
    // The internal API object will be overridden with the received server data.
    Update(ctx context.Context) error
}

// Deletable is an interface which all objects that can be deleted
// using the Client implement.
type Deletable interface {
    // Delete deletes the current resource irreversibly.
    //
    // ErrNotFound is returned if the resource doesn't exist anymore.
    Delete(ctx context.Context) error
}

// Reconcilable is an interface which all objects that can be reconciled
// using the Client implement.
type Reconcilable interface {
    // Reconcile makes sure the desired state in this object (called "req" here) becomes
    // the actual state in the backing Git provider.
    //
    // If req doesn't exist under the hood, it is created (actionTaken == true).
    // If req doesn't equal the actual state, the resource will be updated (actionTaken == true).
    // If req is already the actual state, this is a no-op (actionTaken == false).
    //
    // The internal API object will be overridden with the received server data if actionTaken == true.
    Reconcile(ctx context.Context) (actionTaken bool, err error)
}

In order to access the provider-specific, internal object, all resources implement the gitprovider.Object interface:

// Object is the interface all types should implement.
type Object interface {
    // APIObject returns the underlying value that was returned from the server.
    // This is always a pointer to a struct.
    APIObject() interface{}
}

So, how do I set the desired state for an object before running Update or Reconcile?

Using the Get() {Resource}Info or Set({Resource}Info) error methods. An example as follows, for TeamAccess:

// TeamAccess describes a binding between a repository and a team.
type TeamAccess interface {
    // TeamAccess implements the Object interface,
    // allowing access to the underlying object returned from the API.
    Object
    // The deploy key can be updated.
    Updatable
    // The deploy key can be reconciled.
    Reconcilable
    // The deploy key can be deleted.
    Deletable
    // RepositoryBound returns repository reference details.
    RepositoryBound

    // Get returns high-level information about this team access for the repository.
    Get() TeamAccessInfo
    // Set sets high-level desired state for this team access object. In order to apply these changes in
    // the Git provider, run .Update() or .Reconcile().
    Set(TeamAccessInfo) error
}

// TeamAccessInfo contains high-level information about a team's access to a repository.
type TeamAccessInfo struct {
    // Name describes the name of the team. The team name may contain slashes.
    // +required
    Name string `json:"name"`

    // Permission describes the permission level for which the team is allowed to operate.
    // Default: pull.
    // Available options: See the RepositoryPermission enum.
    // +optional
    Permission *RepositoryPermission `json:"permission,omitempty"`
}

Examples

See the following (automatically tested) examples:

Getting Help

If you have any questions about this library:

Your feedback is always welcome!

License

Apache 2.0

go-git-providers's People

Contributors

dependabot[bot] avatar dinosk avatar foot avatar hiddeco avatar j-thompson12 avatar josecordaz avatar jrryjcksn avatar luxas avatar makkes avatar malarinv avatar michaelbeaumont avatar ranatrk avatar sarataha avatar somtochiama avatar souleb avatar stefanprodan avatar timofurrer avatar yiannistri avatar yitsushi avatar zchee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

go-git-providers's Issues

How to allow testing from forks?

#32 illustrates that Github Actions doesn't "leak" secrets in this repo (for the testing bot) to PRs from forks.
Let's figure out how to allow PRs to be tested with the bot secret enabled.

Incorrect error message on `OrgRepositories().Reconcile()` for GitLab

Describe the bug

I tried to do something that wasn't supported in GitLab, and got an error message referring to GitHub

To Reproduce

  1. Run something that triggers an error in the validateIdentityFields() function. In my case, it was this:
sourceRepoURL := fmt.Sprintf("https://gitlab.com/%s/%s/%s", org, group, sourceRepoName)

ref, err := gitprovider.ParseOrgRepositoryURL(url)

repo, _, err := gp.OrgRepositories().Reconcile(ctx, *ref, gitprovider.RepositoryInfo{
		Description:   gitprovider.StringVar("Integration test repo"),
		Visibility:    gitprovider.RepositoryVisibilityVar(gitprovider.RepositoryVisibilityPrivate),
		DefaultBranch: gitprovider.StringVar(defaultBranch),
}, &gitprovider.RepositoryCreateOptions{AutoInit: gitprovider.BoolVar(true)})

Expected behavior

The error message should refer to GitLab

Actual Behavior

It says stuff about GitHub

Additional context

Problematic line appears to be this:

return fmt.Errorf("github doesn't support sub-organizations: %w", gitprovider.ErrNoProviderSupport)

Add bitbucket stash support

Overview

Add a new gitprodiver, bitbucket stash. The set of features for this new provider must be on par with the existing ones.

Features

  • Add a stash client to communicate with a stash server API #94
  • Add a userService to the stash client #101
  • Add a groupService to the stash client #103
  • Add a projectService to the stash client #104
  • Add gitprovider organisations and teams clients #114
  • Add a gitService to the stash client #105
  • Add a repositoryService to the stash client #107
  • Add gitprovider orgRepository clients #114
  • Add gitprovider userRepository clients #114
  • Add a commitService to the stash client #109
  • Add a branchService to the stash client #108
  • Add a prService to the stash client #110
  • Add a deployKeysService to the stash client #112

References

https://docs.atlassian.com/DAC/rest/stash/3.11.3/stash-rest.html

Add option to get content from all directories recursively

The current implementation to GET a repository's content that was added in #80 does not get a directory tree recursively - it only returns a single, end subdirectory and its files. This is ok for now, but returning all directories recursively could be enabled easily by passing an option with RepositoryContentGetOptions, specifically here.

See documentation:

Alternatively, it would be nice to have the option to pass any RepositoryContentGetOptions :)

gitlab supportedDomain modifies the domain name and returns an url

Describe the bug

Gitlab SupportedDomain function implementation
returns a URL which seems to be breaking the SupportedDomain contract

It also updates the field overwriting the original value

To Reproduce

Create a test case like the following

	clientOptions := []gitprovider.ClientOption{}

	clientOptions = append(clientOptions, gitprovider.WithDomain("gitlab.git.xx"))

	c, err := gitlab.NewClient(os.Getenv("GITLAB_ENTERPRISE_TOKEN"), "", clientOptions...)
	checkErr(err)
	log.Printf("client supported domain: %s", c.SupportedDomain())

it will output a domain name value which is an URL

2022/12/28 22:44:08 client supported domain: https://gitlab.git.xx```

<!--
Steps to reproduce the behaviour
-->

### Expected behavior

A clear and concise description of what you expected to happen.

Given the previous example, I would expect to return a domain name instead of a url

```bash
2022/12/28 22:44:08 client supported domain: gitlab.git.xx```

### Additional context

- Go version: go version go1.19.1 darwin/arm64
- Git provider: gitlab

Gitlab Tree client implementation doesn't support pagination

Describe the bug

  • Gitlab tree is implemented in a pull request.

  • The gitlab client uses the xanzy/go-gitlab module which doesn't support the pagination using keyset mentioned in the gitlab tree docs and gitlab keyset docs. This is based on an existing issue in xanzy/go-gitlab

  • The current maximum number of files retrieved from gitlab trees is 100 files per request.

To Reproduce

Retrieving a tree with more than 100 files+directories combined using the tree client

Expected behavior

Pagination implemented for gitlab tree list(either the user requesting following pages, or internally until all results are retrieved with a maximum number of pages used to limit recursivness)

Additional context

  • Go version: go1.18
  • Git provider: Implemented in pull request

Accept gitlab hosts w/out scheme and use https:// by default

At the moment we have to explicitly include the scheme to the gitlab api server. e.g.

These work:

c, err = NewClient(gitlabToken, "", WithDomain("https://gitlab.works")) // "https://gitlab.works"
c, err = NewClient(gitlabToken, "", WithDomain("http://gitlab.works")) // "http://gitlab.works"

This does not work yet but should prepend https:

c, err = NewClient(gitlabToken, "", WithDomain("gitlab.works")) // "https://gitlab.works"

Deleting files in gitlab does not work

Describe the bug

Deleting a file by setting the Content to nil:

 gitprovider.CommitFile{
	Path:    &path,
	Content: nil,
})

and creating a commit:

commit, err := req.Repository.Commits().Create(ctx, req.HeadBranch, req.CommitMessage, req.Files)

Will panic

panic({0x5c17ce0, 0x75fcb90})
        /usr/local/Cellar/go/1.17.2/libexec/src/runtime/panic.go:1047 +0x266
github.com/fluxcd/go-git-providers/gitlab.(*CommitClient).Create(0xc000f78cd8, {0x1d, 0xc000f62450}, {0xc00011dfe0, 0x1d}, {0xc00091cb10, 0x17}, {0xc0044c9be0, 0x1, 0x1})
        /Users/simon/go/pkg/mod/github.com/fluxcd/[email protected]/gitlab/client_repository_commit.go:78 +0x206

Expected behavior

Don't panic and create a commit that deletes the file.

Additional context

  • Go version: 1.17
  • Git provider: v0.4.1-0.20211222124517-0e29201eb4ac

Consider allowing omitting defaultBranch when creating a repository

  • At the moment we default to master if it is omitted.
  • Github has changed the default to main now, so you have to know that specify that.
  • Gitlab might change their default in the future.

It would be nice to omit the defaultBranch, and then the newly created repo can return whatever the new default branch is.

Integration tests fail when `WithConditionalRequests(true)`

Env:

  • MacOS
  • go 1.14
  • ?
ewq:~/src/go-git-providers(master %=)$ ginkgo -focus "create a repository" github/
Running Suite: GitHub Provider Suite
====================================
Random Seed: 1600436578
Will run 1 of 5 specs

SS
------------------------------
• Failure [3.701 seconds]
GitHub Provider
/Users/simon/src/go-git-providers/github/integration_test.go:127
  should be possible to create a repository [It]
  /Users/simon/src/go-git-providers/github/integration_test.go:216

  Unexpected error:
      <*validation.MultiError | 0xc0002d82e0>: {
          Errors: [
              {
                  Response: {
                      Status: "404 Not Found",
                      StatusCode: 404,
                      Proto: "HTTP/1.1",
                      ProtoMajor: 1,
                      ProtoMinor: 1,
                      Header: {
                         ...snip...
                      }
                  },
                  Message: "Not Found",
                  Errors: nil,
                  Block: nil,
                  DocumentationURL: "https://docs.github.com/rest/reference/repos#get-a-repository",
              },
              {
                  s: "the requested resource was not found",
              },
          ],
      }
      multiple errors occurred:
      - GET https://api.github.com/repos/foot-org/test-repo-517: 404 Not Found []
      - the requested resource was not found
  occurred

  /Users/simon/src/go-git-providers/github/integration_test.go:246
------------------------------
SS

Summarizing 1 Failure:

[Fail] GitHub Provider [It] should be possible to create a repository
/Users/simon/src/go-git-providers/github/integration_test.go:246

Ran 1 of 5 Specs in 4.621 seconds
FAIL! -- 0 Passed | 1 Failed | 0 Pending | 4 Skipped
--- FAIL: TestProvider (4.62s)
FAIL

Ginkgo ran 1 suite in 7.421732423s
Test Suite Failed

disable conditional requests

diff --git a/github/integration_test.go b/github/integration_test.go
index 478bc23..51aacf5 100644
--- a/github/integration_test.go
+++ b/github/integration_test.go
@@ -152,7 +152,7 @@ var _ = Describe("GitHub Provider", func() {
                c, err = NewClient(
                        WithOAuth2Token(githubToken),
                        WithDestructiveAPICalls(true),
-                       WithConditionalRequests(true),
+                       WithConditionalRequests(false),
                        WithPreChainTransportHook(customTransportFactory),
                )
                Expect(err).ToNot(HaveOccurred())

Passes

ewq:~/src/go-git-providers(master *%=)$ ginkgo -focus "create a repository" github/
Running Suite: GitHub Provider Suite
====================================
Random Seed: 1600436820
Will run 1 of 5 specs

SS•SS
Ran 1 of 5 Specs in 4.823 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 4 Skipped
PASS

Ginkgo ran 1 suite in 7.174949645s
Test Suite Passed

Re-enable conditional requests but remove the first lookup

diff --git a/github/integration_test.go b/github/integration_test.go
index 478bc23..5da365e 100644
--- a/github/integration_test.go
+++ b/github/integration_test.go
@@ -227,8 +227,6 @@ var _ = Describe("GitHub Provider", func() {
                // We know that a repo with this name doesn't exist in the organization, let's verify we get an
                // ErrNotFound
                repoRef := newOrgRepoRef(testOrgName, testRepoName)
-               _, err = c.OrgRepositories().Get(ctx, repoRef)
-               Expect(errors.Is(err, gitprovider.ErrNotFound)).To(BeTrue())

                // Create a new repo
                repo, err := c.OrgRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{

Works.

ewq:~/src/go-git-providers(master *%=)$ ginkgo -focus "create a repository" github/
Running Suite: GitHub Provider Suite
====================================
Random Seed: 1600436929
Will run 1 of 5 specs

SS•SS
Ran 1 of 5 Specs in 4.531 seconds
SUCCESS! -- 1 Passed | 0 Failed | 0 Pending | 4 Skipped
PASS

Ginkgo ran 1 suite in 6.867035932s
Test Suite Passed
ewq:~/src/go-git-providers(master *%=)$

Custom domains on gitlab provider do not work

Describe the bug

When using an on-prem GitLab instance, the client needs to be instantiated with a custom domain for that instance using the gitprovider.WithDomain option. Although that method expects only host and port information, when a custom domain is passed without a scheme, it fails when getting a repository with the following error:

Get "<custom domain without scheme>/api/v4/projects/<namespace>%2F<repo>": unsupported protocol scheme ""

Even when I tried to specify a scheme it fails later on when it compares it to the domain of the OrgRepositoryRef (which is scheme-less).

I think the fix should first ensure that the URL that is passed into the gitlab client should include a scheme without the user needing to include it in the gitprovider.WithDomain option, when using custom domains. This is a requirement of the underlying library used since it does not parse the URL correctly when there is no scheme specified (it treats the scheme-less domain as the path of the URL). It should also address the string comparison in this case since this will always fail after comparing https://domain with domain

To Reproduce

ctx := context.Background()
c, err := gitlab.NewClient(os.Getenv("GITLAB_TOKEN"), "", gitprovider.WithDomain("mycustomdomain.com"))
checkErr(err)

ref, err := gitprovider.ParseOrgRepositoryURL("https://mycustomdomain.com/mygroup/myrepo")
checkErr(err)

repo, err := c.OrgRepositories().Get(ctx, *ref)
checkErr(err) // unsupported protocol scheme error

Expected behavior

The repo object is retrieved without any errors using the code above.

Additional context

  • Go version: 1.17.5
  • Git provider: gitlab

`ResourceClient.Organization(o)` should roundtrip to the server

Currently, aquiring an OrganizationClient (or RepositoryClient) can be done through ResourceClient.Organization(orgRef).

This API doesn't signal if the given reference is valid, or even exists in the underlying Git provider. Given this design, such an error would be needed to return errors at usage time, e.g. in OrganizationClient.Teams().List(), which is not great.

I propose to change that to:

ResourceClient.Organization(ctx context.Context, o OrganizationRef) (OrganizationClient, *Organization, error)

which is explicit about that
a) o can be invalid or non-existent
b) a roundtrip to the server is made (adds a context)
c) we get the organization object directly (if we want it), without the need to do an extra ResourceClient.Organizations().Get(ctx, o)

Reconcile on empty GitHub repository returns error

Running Reconcile for a second time on an empty GitHub repository returns the following error(s):

- PATCH https://api.github.com/repos/hiddeco/bootstrap-test-123: 422 Validation Failed [{Resource:Repository Field:default_branch Code:invalid Message:Cannot update default branch for an empty repository. Please init the repository and push first.}]
- PATCH https://api.github.com/repos/hiddeco/bootstrap-test-123: 422 Validation Failed [{Resource:Repository Field:default_branch Code:invalid Message:Cannot update default branch for an empty repository. Please init the repository and push first.}]

Add badges to README

Like go report card, codecov.io, license, contributors, release status, PRs welcome

cacheRoundtripper panic

Should be checking err before accessing StatusCode

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0x17ff5c8]

goroutine 1 [running]:
github.com/fluxcd/go-git-providers/gitprovider/cache.(*cacheRoundtripper).RoundTrip(0xc00054c090, 0xc000170f00, 0xc00054c090, 0x0, 0x0)
        /home/runner/go/pkg/mod/github.com/fluxcd/[email protected]/gitprovider/cache/httpcache.go:71 +0xc8
net/http.send(0xc000170f00, 0x2152f40, 0xc00054c090, 0x0, 0x0, 0x0, 0xc00054c340, 0x203000, 0x1, 0x0)
        /opt/hostedtoolcache/go/1.16.3/x64/src/net/http/client.go:251 +0x454
net/http.(*Client).send(0xc0005acf60, 0xc000170f00, 0x0, 0x0, 0x0, 0xc00054c340, 0x0, 0x1, 0xc000170f00)
        /opt/hostedtoolcache/go/1.16.3/x64/src/net/http/client.go:175 +0xff
net/http.(*Client).do(0xc0005acf60, 0xc000170f00, 0x0, 0x0, 0x0)
        /opt/hostedtoolcache/go/1.16.3/x64/src/net/http/client.go:717 +0x45f
net/http.(*Client).Do(...)

cannot get use repository for gitlab enterprise due to domain and client base url seems to be incorrectly configured / used

Describe the bug

When tried to create a gitlab client for an enterprise instance and then get a repo, i have an error saying that the gitlab
client is not able to consume my repo endpoint.

The issue seems to be an error on how the gitlab client uses `domain (reported here and how it handles base URL.

To Reproduce

scenario 1

  1. use the following gist
  2. configure your gitlab enterprise endpoint and repo
  3. execute the test and you will an error like the client doesn't support handling requests for this domain

scenario 2

  1. use the following gist which is similar to scenario 1 but commenting SupportedDomain call
  2. configure your gitlab enterprise endpoint and repo
  3. execute the test and you will an error like unsupported protocol scheme ""
    In this example, i could see how the underlying client baseurl is not properly created as shown in the image below

Screenshot 2022-12-28 at 23 14 47

Expected behavior

I can use gitlab client for getting repos from gitlab enterprise instances

Additional context

  • Go version: go version go1.19.1 darwin/arm64
  • Git provider: gitlab

Reconcile() should return a boolean to flag if any action was taken

When writing controllers, it is important to know if a Reconcile() call changed anything in the system, or was a no-op.
Hence, we should return a boolean flag in all Reconcile calls, which will make the signature as follows

func (c ClientX) Reconcile(ctx context.Context, before *T, opts ...XReconcileOptions) (after *T, actionTaken bool, err error)

How can I help?

I used to have a similar go library and wound up merging it with one of my projects. I'd love to help contribute to this where I can. What is the best way to get involved?

Add a test case for setting the default branch when creating a repo

Stemming from comment #61 (comment)

To ensure that setting the default branch of a repo at creation time is functional, it should be covered in a test case in the integration tests. Creating a github repo belonging to a user with the default branch set to deploy for example, can be done with:

repo, err := c.OrgRepositories().Create(ctx, repoRef, gitprovider.RepositoryInfo{
			Description: gitprovider.StringVar("foo"),
			DefaultBranch: gitprovider.StringVar("deploy")
		}, &gitprovider.RepositoryCreateOptions{
			AutoInit:        gitprovider.BoolVar(true),
			LicenseTemplate: gitprovider.LicenseTemplateVar(gitprovider.LicenseTemplateApache2),
		})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.