diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 39bd9ac1..ad6ef734 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -1,7 +1,13 @@ +# Dependabot configuration file. version: 2 -enable-beta-ecosystems: true + updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + - package-ecosystem: "pub" directory: "/" schedule: - interval: "weekly" \ No newline at end of file + interval: "monthly" diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml new file mode 100644 index 00000000..2c071305 --- /dev/null +++ b/.github/workflows/create_release.yml @@ -0,0 +1,16 @@ +name: Create Release +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Create a Release + uses: elgohr/Github-Release-Action@v5 + env: + GITHUB_TOKEN: "${{ secrets.RELEASE_TOKEN }}" + with: + title: ${{ github.ref }} diff --git a/.github/workflows/create_tag.yml b/.github/workflows/create_tag.yml new file mode 100644 index 00000000..c5793f0c --- /dev/null +++ b/.github/workflows/create_tag.yml @@ -0,0 +1,25 @@ +name: Release + +# Runs when a PR merges. +# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-workflow-when-a-pull-request-merges +on: + pull_request: + types: + - closed + +jobs: + release: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + container: dart + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: master + - uses: jacopocarlini/action-autotag@3.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index bb1838d7..63b8adab 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,19 +1,33 @@ -name: Dart Checks (analyze,format,publishable) +name: Dart Checks -on: [push] +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: build: - runs-on: ubuntu-latest - container: - image: dart + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + # Test with at least the declared minimum Dart version + sdk: ['3.5', stable] steps: - - uses: actions/checkout@v1 - - name: Install dependencies - run: dart pub get - - name: Dart Analyzer - run: dart analyze - - name: Check Dart Format - run: dart format --set-exit-if-changed -o none lib test tool example integration_test && echo Dart Format πŸ‘ || echo Files needed Dart formatting 😒 - - name: Check if Publishable - run: dart pub publish --dry-run + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + with: + sdk: ${{ matrix.sdk }} + + - name: Install dependencies + run: dart pub get + - name: Dart Analyzer + run: dart analyze + - name: Check Dart Format + if: ${{ matrix.sdk == 'stable' }} + run: dart format --set-exit-if-changed -onone . + - name: Unit tests + run: dart test + - name: Check if Publishable + run: dart pub publish --dry-run diff --git a/.github/workflows/label_prs.yml b/.github/workflows/label_prs.yml deleted file mode 100644 index 8f30b23d..00000000 --- a/.github/workflows/label_prs.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Add PR labels on open -on: - pull_request: - types: [opened] - -jobs: - assignUnreleased: - name: Unreleased - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@master - - name: Apply unreleased label - uses: actions/github-script@v6 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['unreleased'] - }) diff --git a/.github/workflows/publish_demos.yml b/.github/workflows/publish_demos.yml index 25a2bb12..b6b6169e 100644 --- a/.github/workflows/publish_demos.yml +++ b/.github/workflows/publish_demos.yml @@ -10,7 +10,7 @@ jobs: image: dart steps: - name: Checkout πŸ›ŽοΈ - uses: actions/checkout@v2.3.1 + uses: actions/checkout@v4 - name: Install rsync πŸ“š run: | @@ -24,7 +24,7 @@ jobs: rm build/example/packages - name: Publish πŸš€ - uses: JamesIves/github-pages-deploy-action@4.0.0 + uses: JamesIves/github-pages-deploy-action@v4.6.1 with: branch: gh-pages # The branch the action should deploy to. folder: build/example # The folder the action should deploy. diff --git a/.github/workflows/publish_pubdev.yml b/.github/workflows/publish_pubdev.yml new file mode 100644 index 00000000..336e0cf5 --- /dev/null +++ b/.github/workflows/publish_pubdev.yml @@ -0,0 +1,14 @@ +# .github/workflows/publish.yml +name: Publish to pub.dev + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+*' + +# Publish using the reusable workflow from dart-lang. +jobs: + publish: + permissions: + id-token: write # Required for authentication using OIDC + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 \ No newline at end of file diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml deleted file mode 100644 index 658e690d..00000000 --- a/.github/workflows/publish_release.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: Publish to pub.dev - -on: - push: - tags: - - '[0-9]+.[0-9]+.[0-9]+*' - -jobs: - publish: - uses: dart-lang/setup-dart/.github/workflows/publish.yml diff --git a/.github/workflows/release_unreleased_prs.yml b/.github/workflows/release_unreleased_prs.yml deleted file mode 100644 index 9b33737d..00000000 --- a/.github/workflows/release_unreleased_prs.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Release unreleased PRs - -# Runs when a PR merges. -# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-workflow-when-a-pull-request-merges -on: - workflow_dispatch: - pull_request: - types: - - closed - -jobs: - release: - if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest - container: dart - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 2 - ref: master - - name: Release - run: | - git config --global user.name ${{ secrets.USER_NAME }} - git config --global user.email ${{ secrets.USER_EMAIL }} - export PATH="$PATH":"$HOME/.pub-cache/bin" - export GITHUB_TOKEN=${{secrets.MACHINE_GITHUB_API_TOKEN}} - export MACHINE_GITHUB_API_TOKEN=${{secrets.MACHINE_GITHUB_API_TOKEN}} - dart pub get - dart tool/release_unreleased_prs.dart \ No newline at end of file diff --git a/.github/workflows/require_semver_label.yml b/.github/workflows/require_semver_label.yml deleted file mode 100644 index a1b0bbe8..00000000 --- a/.github/workflows/require_semver_label.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Require semver:* and unreleased Label -on: - pull_request: - types: [opened, labeled, unlabeled, synchronize] -jobs: - label: - runs-on: ubuntu-latest - steps: - - uses: mheap/github-action-required-labels@v1 - with: - mode: exactly - count: 1 - labels: "semver:patch, semver:minor, semver:major" - - uses: mheap/github-action-required-labels@v1 - with: - mode: exactly - count: 1 - labels: "unreleased" \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index b5a0c40d..00000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Tests -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - sdk: [2.18.7, stable] # Test with at least the declared minimum Dart version - steps: - - uses: actions/checkout@v2 - - uses: dart-lang/setup-dart@v1.3 - with: - sdk: ${{ matrix.sdk }} - - name: Install dependencies - run: dart pub get - - name: Unit tests - run: dart test - # - name: Integration tests - # run: dart test integration_test diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 90dddd83..4d51d30c 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -8,9 +8,9 @@ jobs: name: Assign Rob runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v4 - name: Apply untriaged label - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | @@ -21,7 +21,7 @@ jobs: labels: ['untriaged','unreleased'] }) - name: Comment On New Issues - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f36eb35..8c92ab04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,98 @@ +## 9.25.0 + +* Require Dart 3.5 +* Require `package:http` `^1.0.0`. +* Fix pagination logic to use `next` link. + +## 9.24.0 + +* Bug fixes to the `Issue.isOpen` and `Issue.isClosed` getters. + +## 9.23.0 + +* Require Dart 3.0. +* Update to the latest `package:lints`. + +## 9.22.0 + +* Add support for the `Ghost` user when the Github user is deleted. + +## 9.21.0 + +* Update MiscService.getApiStatus() to use the v2 API + * `APIStatus` has been refactored to match, now exposing `page` and `status` + +## 9.20.0 + +* Add a Changes object to the PullRequestEvent object so we can see what changed in edited PR events by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/390 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.19.0...9.20.0 + +## 9.19.0 + +* Revert "Add the 'PushEvent' webhook and associated PushCommit object" by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/387 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.18.0...9.19.0 + +## 9.18.0 + +- Bad Release. Was: Add the 'PushEvent' webhook and associated PushCommit + +## 9.17.0 + +* Add bearerToken constructor to Authentication class by @kevmoo in https://github.com/SpinlockLabs/github.dart/pull/381 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.16.0...9.17.0 + +## 9.16.0 + +* Fix links and spelling nits in markdown files by @kevmoo in https://github.com/SpinlockLabs/github.dart/pull/379 +* Support latest pkg:http by @kevmoo in https://github.com/SpinlockLabs/github.dart/pull/380 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.15.1...9.16.0 + +## 9.15.1 + +* Revert immutable auth by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/378 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.15.0...9.15.1 + +## 9.15.0 + +* Implement IssuesService.lock/unlock by @Hixie in https://github.com/SpinlockLabs/github.dart/pull/376 +* Bump JamesIves/github-pages-deploy-action from 4.4.1 to 4.4.2 by @dependabot in https://github.com/SpinlockLabs/github.dart/pull/371 +* Make GitHub.auth non-nullable by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/377 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.14.0...9.15.0 + +## 9.14.0 + +* Add optional filter params on Repositories.listCommits by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/368 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.13.0...9.14.0 + +## 9.13.0 + +* Add node_id to the pull request model by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/367 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.12.0...9.13.0 + +## 9.12.0 + +* Add support for issue and PR timeline events via `Issue.listTimeline`. + +## 9.11.0 + +* expose IssueLabel.description; update labels REST APIs by @devoncarew in https://github.com/SpinlockLabs/github.dart/pull/355 + ## 9.10.1 * Pass required User-Agent HTTP header on all requests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ac480095..b574e5a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ GitHub.dart is of course Open Source! We love it when people contribute! ## Getting Started - Make sure you have a [GitHub Account](https://github.com/signup/free). -- Make sure the [Dart SDK](https://www.dartlang.org/tools/sdk/) is installed on your system. +- Make sure the [Dart SDK](https://dart.dev/tools/sdk) is installed on your system. - Make sure you have [Git](http://git-scm.com/) installed on your system. - [Fork](https://help.github.com/articles/fork-a-repo) the [repository](https://github.com/SpinlockLabs/github.dart) on GitHub. @@ -15,12 +15,12 @@ GitHub.dart is of course Open Source! We love it when people contribute! - [Commit your code](http://git-scm.com/book/en/Git-Basics-Recording-Changes-to-the-Repository) for each logical change (see [tips for creating better commit messages](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message)). - [Push your change](https://help.github.com/articles/pushing-to-a-remote) to your fork. - [Create a Pull Request](https://help.github.com/articles/creating-a-pull-request) on GitHub for your change. -- Wait for reviewers (usually kaendfinger) to give feedback. +- Wait for reviewers (usually robrbecker) to give feedback. - When the reviewers think that the Pull Request is ready, they will merge it. ## Code Style -GitHub.dart follows the [Dart Style Guide](https://www.dartlang.org/articles/style-guide/). Please note that if your code is not formatted according to the guide as much as possible, we will reject your Pull Request until it is fixed. Some things such as long lines will generally be accepted, however try to make it smaller if possible. +GitHub.dart follows the [Dart Style Guide](https://dart.dev/effective-dart/style). Please note that if your code is not formatted according to the guide as much as possible, we will reject your Pull Request until it is fixed. Some things such as long lines will generally be accepted, however try to make it smaller if possible. ## Efficiency @@ -28,33 +28,38 @@ GitHub.dart is committed to efficiency as much as possible. If your code is not ## Rejections -Pull Request rejections are not a bad thing. It just means you need to fix something. Perhaps it is important to define 'rejection' as it is used in this case. A rejection is when a GitHub.dart committer comments on a Pull Request with a comment like 'rejected due to incorrect formatting'. +Pull Request rejections are not a bad thing. It just means you need to fix something. Perhaps it is important to define 'rejection' as it is used in this case. A rejection is when a `GitHub.dart` committer comments on a Pull Request with a comment like 'rejected due to incorrect formatting'. -## Contacting Us +## Generated code + +To regenerate the JSON logic for the models, run: + +```sh +dart run build_runner build -d +``` -- IRC: `#directcode on irc.esper.net and irc.freenode.net` -- Email: `kaendfinger@gmail.com` +## Tests -## Becoming a Committer +`dart test` will only run the unit tests. -If you get on IRC and ask us, we can review your work and add you as a committer if we think you should have it. +To run the complete test suite you will need to install +`octokit/fixtures-server`. -## Releasing & Publishing +``` +npm install --global @octokit/fixtures-server +``` + +Tests can be run using `make test`, which will start up a local mock +GitHub and execute tests against it using your localhost port 3000. + +## Contacting Us -This repo is now configured to release after every PR merge. This means a couple things for PRs that are put up: +File issues at https://github.com/SpinlockLabs/github.dart/issues -1. A semver label is required. A github check will remind you that you need one. Reviewers should check that it is correct. See https://semver.org/ to understand more. -2. There is no need to modify the version in the pubspec.yaml in your PRs. The tooling will update the version according to the semver label applied to your PR. -3. Same thing for the CHANGELOG.md. Tooling will update it automatically after merge. -4. A github release will be created and published to pub.dev for you. +## Releases -For example if your PR has `semver:minor` label applied and the latest version is 1.2.3, once merged, the tooling will: -- update the pubspec.yaml to 1.3.0 -- Add the github auto-generated release notes with 1.3.0 to the top of the CHANGELOG.md -- Create a release in github for 1.3.0 (which creates a git tag of 1.3.0) -- Remove the `unreleased` label from the PR and add the `released` label -- Comment on the PR stating the version that it was released in and link to the release -- When the release is created, it will automatically be published to pub.dev +Merged pull requests that edit the `pubspec.yaml` version will create new releases. +Once CI is green, it will create a tag for that commit based on the version, which +gets published by pub.dev. -NOTE: If you want the ability to merge a PR **WITHOUT** automatically releasing and publishing, simply add the `no_release_on_merge` label before merging. Do note that the PR has been merged though and whatever the next PR is that triggers a release will release and publish everything that has been merged. So if you want to batch a few PRs into 1 release, label them all `no_release_on_merge`. Then whichever is the last to be merged, remove that label before merging to trigger the release. -You may also manually trigger the action to release unreleased PRs from the Actions tab in Github. +If no new version was created, nothing will be published. diff --git a/README.md b/README.md index fc8aa669..08dc8e09 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # GitHub for Dart -![](https://github.com/SpinlockLabs/github.dart/workflows/Dart%20CI/badge.svg) -[![Pub](https://img.shields.io/pub/v/github.svg)](https://pub.dartlang.org/packages/github) +[![Dart Checks](https://github.com/SpinlockLabs/github.dart/actions/workflows/dart.yml/badge.svg)](https://github.com/SpinlockLabs/github.dart/actions/workflows/dart.yml) +[![Pub](https://img.shields.io/pub/v/github.svg)](https://pub.dev/packages/github) This is a library for interacting with GitHub in Dart. It works on all platforms including web, server, and Flutter. Please submit issues and pull requests, help out, or just give encouragement. -**Notice**: This is not an official Github project. It is maintained by volunteers. +**Notice**: This is not an official GitHub project. It is maintained by volunteers. We are looking for contributors. If you're interested or have questions, head over to discussions https://github.com/SpinlockLabs/github.dart/discussions ## Features - Works on the Server, Browser, and Flutter - Really Fast -- Plugable API +- Pluggable API - Supports Authentication - Builtin OAuth2 Flow - Hook Server Helper @@ -21,7 +21,7 @@ We are looking for contributors. If you're interested or have questions, head ov ## Links - [Library Demos](https://spinlocklabs.github.io/github.dart/) (based on the [sample code](https://github.com/SpinlockLabs/github.dart/tree/master/example)) -- [Pub Package](https://pub.dartlang.org/packages/github) +- [Pub Package](https://pub.dev/packages/github) - [Wiki](https://github.com/SpinlockLabs/github.dart/wiki) - [Latest API reference](https://pub.dev/documentation/github/latest/) @@ -35,4 +35,4 @@ Post a question or idea: https://github.com/SpinlockLabs/github.dart/discussions ## Star History -[![Star History Chart](https://api.star-history.com/svg?repos=SpinlockLabs/github.dart&type=Date)](https://star-history.com/#SpinlockLabs/github.dart&Date) \ No newline at end of file +[![Star History Chart](https://api.star-history.com/svg?repos=SpinlockLabs/github.dart&type=Date)](https://star-history.com/#SpinlockLabs/github.dart&Date) diff --git a/analysis_options.yaml b/analysis_options.yaml index f7a9ff49..f2974469 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -15,8 +15,6 @@ linter: - avoid_implementing_value_types - avoid_js_rounded_ints - avoid_private_typedef_functions - - avoid_returning_null - - avoid_returning_null_for_future - avoid_returning_this - avoid_setters_without_getters - avoid_slow_async_io @@ -33,7 +31,6 @@ linter: - omit_local_variable_types - one_member_abstracts - only_throw_errors - - package_api_docs - prefer_asserts_in_initializer_lists - prefer_const_constructors - prefer_const_constructors_in_immutables diff --git a/example/common.dart b/example/common.dart index 1564a6c3..e508d0ee 100644 --- a/example/common.dart +++ b/example/common.dart @@ -1,4 +1,5 @@ import 'dart:async'; +// ignore: deprecated_member_use import 'dart:html'; import 'package:github/github.dart'; @@ -13,7 +14,6 @@ export 'package:github/github.dart'; Future initViewSourceButton(String script) async { // query the DOM for the view source button, handle clicks document.querySelector('#view-source')?.onClick.listen((_) { - // ignore: unsafe_html final popup = window.open( 'https://github.com/SpinlockLabs/github.dart/blob/master/example/$script', 'View Source'); diff --git a/example/emoji.dart b/example/emoji.dart index 7604d461..663269dd 100644 --- a/example/emoji.dart +++ b/example/emoji.dart @@ -1,4 +1,5 @@ import 'dart:async'; +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/index.dart b/example/index.dart index 3a0f10b2..535acd07 100644 --- a/example/index.dart +++ b/example/index.dart @@ -1,9 +1,11 @@ +// ignore: deprecated_member_use import 'dart:html'; + import 'common.dart'; void main() { final tokenInput = querySelector('#token') as InputElement; - tokenInput.value = github.auth!.token ?? ''; + tokenInput.value = github.auth.token ?? ''; window.sessionStorage['GITHUB_TOKEN'] = tokenInput.value!; tokenInput.onKeyUp.listen((_) { window.sessionStorage['GITHUB_TOKEN'] = tokenInput.value!; diff --git a/example/languages.dart b/example/languages.dart index 02da85e0..aa8b7ec1 100644 --- a/example/languages.dart +++ b/example/languages.dart @@ -1,3 +1,4 @@ +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; @@ -34,7 +35,6 @@ void reloadTable({int accuracy = 4}) { isReloadingTable = true; final md = generateMarkdown(accuracy); github.misc.renderMarkdown(md).then((html) { - // ignore: unsafe_html tableDiv!.setInnerHtml(html, treeSanitizer: NodeTreeSanitizer.trusted); isReloadingTable = false; }); diff --git a/example/organization.dart b/example/organization.dart index ef7135f8..d11d0fdd 100644 --- a/example/organization.dart +++ b/example/organization.dart @@ -1,4 +1,5 @@ import 'dart:async'; +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/pr.dart b/example/pr.dart index 15e18180..660d4c45 100644 --- a/example/pr.dart +++ b/example/pr.dart @@ -1,4 +1,5 @@ import 'dart:async'; +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/readme.dart b/example/readme.dart index ad9ec300..1920cca4 100644 --- a/example/readme.dart +++ b/example/readme.dart @@ -1,3 +1,4 @@ +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/release_notes.dart b/example/release_notes.dart index be929744..27835cdd 100644 --- a/example/release_notes.dart +++ b/example/release_notes.dart @@ -1,3 +1,4 @@ +// ignore: deprecated_member_use import 'dart:html'; import 'package:pub_semver/pub_semver.dart'; diff --git a/example/releases.dart b/example/releases.dart index ddd19570..c244c962 100644 --- a/example/releases.dart +++ b/example/releases.dart @@ -1,3 +1,4 @@ +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/repos.dart b/example/repos.dart index e7b21da4..409417ab 100644 --- a/example/repos.dart +++ b/example/repos.dart @@ -1,4 +1,5 @@ import 'dart:async'; +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/search.dart b/example/search.dart index 9ca8b0ac..aeee9cbb 100644 --- a/example/search.dart +++ b/example/search.dart @@ -1,4 +1,6 @@ +// ignore: deprecated_member_use import 'dart:html'; + import 'common.dart'; Future main() async { diff --git a/example/stars.dart b/example/stars.dart index 52a95e54..2bc50b4c 100644 --- a/example/stars.dart +++ b/example/stars.dart @@ -1,3 +1,4 @@ +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/user_info.dart b/example/user_info.dart index a5113646..656207b1 100644 --- a/example/user_info.dart +++ b/example/user_info.dart @@ -1,3 +1,4 @@ +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; @@ -59,8 +60,8 @@ void loadUser() { }); }); - if (github.auth!.token != null) { - localToken!.value = github.auth!.token; + if (github.auth.token != null) { + localToken!.value = github.auth.token; loadBtn.click(); } } diff --git a/example/users.dart b/example/users.dart index ab75a061..003d3f5d 100644 --- a/example/users.dart +++ b/example/users.dart @@ -1,4 +1,5 @@ import 'dart:async'; +// ignore: deprecated_member_use import 'dart:html'; import 'common.dart'; diff --git a/example/zen.dart b/example/zen.dart index 2e4d8b57..34c55c87 100644 --- a/example/zen.dart +++ b/example/zen.dart @@ -1,4 +1,6 @@ +// ignore: deprecated_member_use import 'dart:html'; + import 'common.dart'; Future main() async { diff --git a/lib/browser_helper.dart b/lib/browser_helper.dart index 0d9af0f1..39ebd393 100644 --- a/lib/browser_helper.dart +++ b/lib/browser_helper.dart @@ -1,4 +1,6 @@ +// ignore: deprecated_member_use import 'dart:html'; + import 'package:github/src/common.dart'; /// Renders Markdown in HTML using the GitHub API @@ -24,7 +26,6 @@ void renderMarkdown(GitHub github, String selector, {int indent = 4}) { e.hidden = false; e.setAttribute('rendered', ''); e.classes.add('markdown-body'); - // ignore: unsafe_html e.setInnerHtml(html, treeSanitizer: NodeTreeSanitizer.trusted); }); } diff --git a/lib/hooks.dart b/lib/hooks.dart index 1a336ed2..19fc3fae 100644 --- a/lib/hooks.dart +++ b/lib/hooks.dart @@ -7,7 +7,6 @@ /// Add this import if you are in a non-web environment and writing something /// that uses github hooks. For more information, see github hooks documentation /// https://developer.github.com/v3/repos/hooks/ - -library hooks; +library; export 'src/server/xplat_server.dart'; diff --git a/lib/src/browser/xplat_browser.dart b/lib/src/browser/xplat_browser.dart index 79aeeb17..c54a2530 100644 --- a/lib/src/browser/xplat_browser.dart +++ b/lib/src/browser/xplat_browser.dart @@ -1,4 +1,6 @@ +// ignore: deprecated_member_use import 'dart:html'; + import 'package:github/src/common.dart'; import 'package:github/src/common/xplat_common.dart' show findAuthenticationInMap; @@ -11,7 +13,7 @@ Authentication findAuthenticationFromEnvironment() { // search the query string parameters first var auth = findAuthenticationInMap(_parseQuery(window.location.href)); auth ??= findAuthenticationInMap(window.sessionStorage); - return auth ?? Authentication.anonymous(); + return auth ?? const Authentication.anonymous(); } /// Parse the query string to a parameter `Map` diff --git a/lib/src/common.dart b/lib/src/common.dart index f8be345c..df783a26 100644 --- a/lib/src/common.dart +++ b/lib/src/common.dart @@ -1,5 +1,7 @@ /// The Core of GitHub for Dart. /// Contains the Models and other GitHub stuff. +library; + export 'package:github/src/common/activity_service.dart'; export 'package:github/src/common/authorizations_service.dart'; export 'package:github/src/common/checks_service.dart'; @@ -31,6 +33,8 @@ export 'package:github/src/common/model/repos_releases.dart'; export 'package:github/src/common/model/repos_stats.dart'; export 'package:github/src/common/model/repos_statuses.dart'; export 'package:github/src/common/model/search.dart'; +export 'package:github/src/common/model/timeline.dart'; +export 'package:github/src/common/model/timeline_support.dart'; export 'package:github/src/common/model/users.dart'; export 'package:github/src/common/orgs_service.dart'; export 'package:github/src/common/pulls_service.dart'; diff --git a/lib/src/common/activity_service.dart b/lib/src/common/activity_service.dart index 0001fcdc..f97aefcf 100644 --- a/lib/src/common/activity_service.dart +++ b/lib/src/common/activity_service.dart @@ -9,7 +9,7 @@ import 'package:http/http.dart' as http; /// /// API docs: https://developer.github.com/v3/activity/ class ActivityService extends Service { - ActivityService(GitHub github) : super(github); + ActivityService(super.github); /// Lists public events. /// @@ -329,7 +329,7 @@ class EventPoller { final List handledEvents = []; Timer? _timer; - StreamController? _controller; // ignore: close_sinks + StreamController? _controller; String? _lastFetched; diff --git a/lib/src/common/authorizations_service.dart b/lib/src/common/authorizations_service.dart index 3cbe6ef0..68b80c25 100644 --- a/lib/src/common/authorizations_service.dart +++ b/lib/src/common/authorizations_service.dart @@ -10,7 +10,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/oauth_authorizations/ class AuthorizationsService extends Service { - AuthorizationsService(GitHub github) : super(github); + AuthorizationsService(super.github); /// Lists all authorizations. /// diff --git a/lib/src/common/checks_service.dart b/lib/src/common/checks_service.dart index 0e87f4d2..f3085197 100644 --- a/lib/src/common/checks_service.dart +++ b/lib/src/common/checks_service.dart @@ -18,14 +18,13 @@ class ChecksService extends Service { /// API docs: https://developer.github.com/v3/checks/suites/ final CheckSuitesService checkSuites; - ChecksService(GitHub github) + ChecksService(super.github) : checkRuns = CheckRunsService._(github), - checkSuites = CheckSuitesService._(github), - super(github); + checkSuites = CheckSuitesService._(github); } class CheckRunsService extends Service { - CheckRunsService._(GitHub github) : super(github); + CheckRunsService._(super.github); /// Creates a new check run for a specific commit in a repository. /// Your GitHub App must have the `checks:write` permission to create check runs. @@ -235,7 +234,7 @@ class CheckRunsService extends Service { } class CheckSuitesService extends Service { - CheckSuitesService._(GitHub github) : super(github); + CheckSuitesService._(super.github); /// Gets a single check suite using its `id`. /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to get check suites. diff --git a/lib/src/common/gists_service.dart b/lib/src/common/gists_service.dart index 9078e03d..05ac62bd 100644 --- a/lib/src/common/gists_service.dart +++ b/lib/src/common/gists_service.dart @@ -8,7 +8,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/gists/ class GistsService extends Service { - GistsService(GitHub github) : super(github); + GistsService(super.github); /// lists gists for a user. /// diff --git a/lib/src/common/git_service.dart b/lib/src/common/git_service.dart index 7052e4f6..338dbeba 100644 --- a/lib/src/common/git_service.dart +++ b/lib/src/common/git_service.dart @@ -8,7 +8,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/git/blobs/ class GitService extends Service { - const GitService(GitHub github) : super(github); + const GitService(super.github); /// Fetches a blob from [slug] for a given [sha]. /// diff --git a/lib/src/common/github.dart b/lib/src/common/github.dart index 0aead228..e6ba64cb 100644 --- a/lib/src/common/github.dart +++ b/lib/src/common/github.dart @@ -19,12 +19,11 @@ class GitHub { /// [endpoint] is the api endpoint to use /// [auth] is the authentication information GitHub({ - Authentication? auth, + this.auth = const Authentication.anonymous(), this.endpoint = 'https://api.github.com', this.version = '2022-11-28', http.Client? client, - }) : auth = auth ?? Authentication.anonymous(), - client = client ?? http.Client(); + }) : client = client ?? http.Client(); static const _ratelimitLimitHeader = 'x-ratelimit-limit'; static const _ratelimitResetHeader = 'x-ratelimit-reset'; @@ -34,7 +33,7 @@ class GitHub { static const versionHeader = 'X-GitHub-Api-Version'; /// Authentication Information - Authentication? auth; + Authentication auth; /// API Endpoint final String endpoint; @@ -191,7 +190,7 @@ class GitHub { /// /// The future will pass the object returned from this function to the then method. /// The default [convert] function returns the input object. - /// [body] is the data to send to the server. Pass in a List if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [body] is the data to send to the server. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content /// [S] represents the input type. /// [T] represents the type return from this function after conversion Future postJSON( @@ -233,7 +232,7 @@ class GitHub { /// /// The future will pass the object returned from this function to the then method. /// The default [convert] function returns the input object. - /// [body] is the data to send to the server. Pass in a List if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [body] is the data to send to the server. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content /// [S] represents the input type. /// [T] represents the type return from this function after conversion Future putJSON( @@ -275,7 +274,7 @@ class GitHub { /// /// The future will pass the object returned from this function to the then method. /// The default [convert] function returns the input object. - /// [body] is the data to send to the server. Pass in a List if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [body] is the data to send to the server. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content /// [S] represents the input type. /// [T] represents the type return from this function after conversion Future patchJSON( @@ -344,7 +343,7 @@ class GitHub { /// [path] can either be a path like '/repos' or a full url. /// [headers] are HTTP Headers. If it doesn't exist, the 'Accept' and 'Authorization' headers are added. /// [params] are query string parameters. - /// [body] is the body content of requests that take content. Pass in a List if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [body] is the body content of requests that take content. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content /// Future request( String method, @@ -369,16 +368,13 @@ class GitHub { headers['Accept'] = preview; } - if (auth!.isToken) { - headers.putIfAbsent('Authorization', () => 'token ${auth!.token}'); - } else if (auth!.isBasic) { - final userAndPass = - base64Encode(utf8.encode('${auth!.username}:${auth!.password}')); - headers.putIfAbsent('Authorization', () => 'basic $userAndPass'); + final authHeaderValue = auth.authorizationHeaderValue(); + if (authHeaderValue != null) { + headers.putIfAbsent('Authorization', () => authHeaderValue); } // See https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#user-agent-required - headers.putIfAbsent('User-Agent', () => auth?.username ?? 'github.dart'); + headers.putIfAbsent('User-Agent', () => auth.username ?? 'github.dart'); if (method == 'PUT' && body == null) { headers.putIfAbsent('Content-Length', () => '0'); @@ -433,7 +429,6 @@ class GitHub { /// Internal method to handle status codes /// Never handleStatusCode(http.Response response) { - print(response.body); String? message = ''; List>? errors; if (response.headers['content-type']!.contains('application/json')) { @@ -450,7 +445,7 @@ class GitHub { } } } catch (ex) { - print(ex); + throw UnknownError(this, ex.toString()); } } switch (response.statusCode) { @@ -494,10 +489,6 @@ class GitHub { /// Disposes of this GitHub Instance. /// No other methods on this instance should be called after this method is called. void dispose() { - // Destroy the Authentication Information - // This is needed for security reasons. - auth = null; - // Closes the HTTP Client client.close(); } diff --git a/lib/src/common/issues_service.dart b/lib/src/common/issues_service.dart index 490396fb..ed061846 100644 --- a/lib/src/common/issues_service.dart +++ b/lib/src/common/issues_service.dart @@ -8,7 +8,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/issues/ class IssuesService extends Service { - IssuesService(GitHub github) : super(github); + IssuesService(super.github); /// List all issues across all the authenticated user’s visible repositories /// including owned repositories, member repositories, and organization repositories @@ -290,19 +290,49 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/labels/#create-a-label Future createLabel( - RepositorySlug slug, String name, String color) { - return github.postJSON('/repos/${slug.fullName}/labels', - body: GitHubJson.encode({'name': name, 'color': color}), - convert: IssueLabel.fromJson); + RepositorySlug slug, + String name, { + String? color, + String? description, + }) { + return github.postJSON( + '/repos/${slug.fullName}/labels', + body: GitHubJson.encode({ + 'name': name, + if (color != null) 'color': color, + if (description != null) 'description': description, + }), + convert: IssueLabel.fromJson, + ); } /// Edits a label. /// /// API docs: https://developer.github.com/v3/issues/labels/#update-a-label + @Deprecated('See updateLabel instead.') Future editLabel(RepositorySlug slug, String name, String color) { - return github.postJSON('/repos/${slug.fullName}/labels/$name', - body: GitHubJson.encode({'name': name, 'color': color}), - convert: IssueLabel.fromJson); + return updateLabel(slug, name, color: color); + } + + /// Update a label. + /// + /// API docs: https://developer.github.com/v3/issues/labels/#update-a-label + Future updateLabel( + RepositorySlug slug, + String name, { + String? newName, + String? color, + String? description, + }) { + return github.patchJSON( + '/repos/${slug.fullName}/labels/$name', + body: GitHubJson.encode({ + if (newName != null) 'new_name': newName, + if (color != null) 'color': color, + if (description != null) 'description': description, + }), + convert: IssueLabel.fromJson, + ); } /// Deletes a label. @@ -402,4 +432,41 @@ class IssuesService extends Service { .request('DELETE', '/repos/${slug.fullName}/milestones/$number') .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } + + /// Lists all timeline events for an issue. + /// + /// API docs: https://docs.github.com/en/rest/issues/timeline?apiVersion=2022-11-28 + Stream listTimeline(RepositorySlug slug, int issueNumber) { + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/issues/$issueNumber/timeline', + TimelineEvent.fromJson, + ); + } + + /// Lock an issue. + /// + /// API docs: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#lock-an-issue + /// + /// The `lockReason`, if specified, must be one of: `off-topic`, `too heated`, `resolved`, `spam`. + Future lock(RepositorySlug slug, int number, + {String? lockReason}) async { + String body; + if (lockReason != null) { + body = GitHubJson.encode({'lock_reason': lockReason}); + } else { + body = '{}'; + } + await github.postJSON('/repos/${slug.fullName}/issues/$number/lock', + body: body, statusCode: 204); + } + + /// Unlock an issue. + /// + /// API docs: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#unlock-an-issue + Future unlock(RepositorySlug slug, int number) async { + await github.request( + 'DELETE', '/repos/${slug.fullName}/issues/$number/lock', + statusCode: 204); + } } diff --git a/lib/src/common/misc_service.dart b/lib/src/common/misc_service.dart index 4951848f..30385a18 100644 --- a/lib/src/common/misc_service.dart +++ b/lib/src/common/misc_service.dart @@ -7,7 +7,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/misc/ class MiscService extends Service { - MiscService(GitHub github) : super(github); + MiscService(super.github); /// Fetches all emojis available on GitHub /// Returns a map of the name to a url of the image. @@ -68,8 +68,10 @@ class MiscService extends Service { } /// Gets the GitHub API Status. + /// + /// API docs: https://www.githubstatus.com/api Future getApiStatus() => - github.getJSON('https://status.github.com/api/status.json', + github.getJSON('https://status.github.com/api/v2/status.json', statusCode: StatusCodes.OK, convert: APIStatus.fromJson); /// Returns an ASCII Octocat with the specified [text]. diff --git a/lib/src/common/model/authorizations.g.dart b/lib/src/common/model/authorizations.g.dart index 7c3a2e73..918a8a47 100644 --- a/lib/src/common/model/authorizations.g.dart +++ b/lib/src/common/model/authorizations.g.dart @@ -8,7 +8,7 @@ part of 'authorizations.dart'; Authorization _$AuthorizationFromJson(Map json) => Authorization( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), scopes: (json['scopes'] as List?)?.map((e) => e as String).toList(), token: json['token'] as String?, diff --git a/lib/src/common/model/changes.dart b/lib/src/common/model/changes.dart new file mode 100644 index 00000000..edccdbc5 --- /dev/null +++ b/lib/src/common/model/changes.dart @@ -0,0 +1,68 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'changes.g.dart'; + +@immutable +@JsonSerializable() +class Ref { + const Ref(this.from); + final String? from; + + factory Ref.fromJson(Map input) => _$RefFromJson(input); + Map toJson() => _$RefToJson(this); +} + +@immutable +@JsonSerializable() +class Sha { + const Sha(this.from); + final String? from; + + factory Sha.fromJson(Map input) => _$ShaFromJson(input); + Map toJson() => _$ShaToJson(this); +} + +@immutable +@JsonSerializable() +class Base { + const Base(this.ref, this.sha); + final Ref? ref; + final Sha? sha; + + factory Base.fromJson(Map input) => _$BaseFromJson(input); + Map toJson() => _$BaseToJson(this); +} + +@immutable +@JsonSerializable() +class Body { + const Body(this.from); + final String? from; + + factory Body.fromJson(Map input) => _$BodyFromJson(input); + Map toJson() => _$BodyToJson(this); +} + +@immutable +@JsonSerializable() +class Title { + const Title({this.from}); + final String? from; + + factory Title.fromJson(Map input) => _$TitleFromJson(input); + Map toJson() => _$TitleToJson(this); +} + +@immutable +@JsonSerializable() +class Changes { + const Changes(this.base, this.body, this.title); + final Base? base; + final Body? body; + final Title? title; + + factory Changes.fromJson(Map input) => + _$ChangesFromJson(input); + Map toJson() => _$ChangesToJson(this); +} diff --git a/lib/src/common/model/changes.g.dart b/lib/src/common/model/changes.g.dart new file mode 100644 index 00000000..13e97d0e --- /dev/null +++ b/lib/src/common/model/changes.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'changes.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Ref _$RefFromJson(Map json) => Ref( + json['from'] as String?, + ); + +Map _$RefToJson(Ref instance) => { + 'from': instance.from, + }; + +Sha _$ShaFromJson(Map json) => Sha( + json['from'] as String?, + ); + +Map _$ShaToJson(Sha instance) => { + 'from': instance.from, + }; + +Base _$BaseFromJson(Map json) => Base( + json['ref'] == null + ? null + : Ref.fromJson(json['ref'] as Map), + json['sha'] == null + ? null + : Sha.fromJson(json['sha'] as Map), + ); + +Map _$BaseToJson(Base instance) => { + 'ref': instance.ref, + 'sha': instance.sha, + }; + +Body _$BodyFromJson(Map json) => Body( + json['from'] as String?, + ); + +Map _$BodyToJson(Body instance) => { + 'from': instance.from, + }; + +Title _$TitleFromJson(Map json) => Title( + from: json['from'] as String?, + ); + +Map _$TitleToJson(Title instance) => { + 'from': instance.from, + }; + +Changes _$ChangesFromJson(Map json) => Changes( + json['base'] == null + ? null + : Base.fromJson(json['base'] as Map), + json['body'] == null + ? null + : Body.fromJson(json['body'] as Map), + json['title'] == null + ? null + : Title.fromJson(json['title'] as Map), + ); + +Map _$ChangesToJson(Changes instance) => { + 'base': instance.base, + 'body': instance.body, + 'title': instance.title, + }; diff --git a/lib/src/common/model/checks.dart b/lib/src/common/model/checks.dart index 9d7d6e21..b7666579 100644 --- a/lib/src/common/model/checks.dart +++ b/lib/src/common/model/checks.dart @@ -9,7 +9,7 @@ class CheckRunAnnotationLevel extends EnumWithValue { static const warning = CheckRunAnnotationLevel._('warning'); static const failure = CheckRunAnnotationLevel._('failure'); - const CheckRunAnnotationLevel._(String value) : super(value); + const CheckRunAnnotationLevel._(String super.value); factory CheckRunAnnotationLevel._fromValue(String? value) { switch (value) { @@ -51,10 +51,10 @@ class CheckRunConclusion extends EnumWithValue { static const actionRequired = CheckRunConclusion._('action_required'); static const empty = CheckRunConclusion._(null); - const CheckRunConclusion._(String? value) : super(value); + const CheckRunConclusion._(super.value); factory CheckRunConclusion._fromValue(String? value) { - if (value == null) { + if (value == null || value == 'null') { return empty; } for (final level in const [ @@ -79,13 +79,13 @@ class CheckRunStatus extends EnumWithValue { static const queued = CheckRunStatus._('queued'); static const inProgress = CheckRunStatus._('in_progress'); static const completed = CheckRunStatus._('completed'); - const CheckRunStatus._(String value) : super(value); + const CheckRunStatus._(String super.value); } class CheckRunFilter extends EnumWithValue { static const all = CheckRunFilter._('all'); static const latest = CheckRunFilter._('latest'); - const CheckRunFilter._(String value) : super(value); + const CheckRunFilter._(String super.value); } @immutable @@ -253,7 +253,7 @@ class CheckRunAnnotation { assert(title.length <= 255); @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (other is CheckRunAnnotation) { return other.annotationLevel == annotationLevel && other.path == path && diff --git a/lib/src/common/model/gists.g.dart b/lib/src/common/model/gists.g.dart index e1d6db23..3438e5ea 100644 --- a/lib/src/common/model/gists.g.dart +++ b/lib/src/common/model/gists.g.dart @@ -20,7 +20,7 @@ Gist _$GistFromJson(Map json) => Gist( (k, e) => MapEntry(k, GistFile.fromJson(e as Map)), ), htmlUrl: json['html_url'] as String?, - commentsCount: json['comments'] as int?, + commentsCount: (json['comments'] as num?)?.toInt(), gitPullUrl: json['git_pull_url'] as String?, gitPushUrl: json['git_push_url'] as String?, createdAt: json['created_at'] == null @@ -48,7 +48,7 @@ Map _$GistToJson(Gist instance) => { GistFile _$GistFileFromJson(Map json) => GistFile( filename: json['filename'] as String?, - size: json['size'] as int?, + size: (json['size'] as num?)?.toInt(), rawUrl: json['raw_url'] as String?, type: json['type'] as String?, language: json['language'] as String?, @@ -70,7 +70,7 @@ GistFork _$GistForkFromJson(Map json) => GistFork( user: json['user'] == null ? null : User.fromJson(json['user'] as Map), - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -92,9 +92,9 @@ GistHistoryEntry _$GistHistoryEntryFromJson(Map json) => user: json['user'] == null ? null : User.fromJson(json['user'] as Map), - deletions: json['change_status/deletions'] as int?, - additions: json['change_status/additions'] as int?, - totalChanges: json['change_status/total'] as int?, + deletions: (json['change_status/deletions'] as num?)?.toInt(), + additions: (json['change_status/additions'] as num?)?.toInt(), + totalChanges: (json['change_status/total'] as num?)?.toInt(), committedAt: json['committed_at'] == null ? null : DateTime.parse(json['committed_at'] as String), @@ -111,7 +111,7 @@ Map _$GistHistoryEntryToJson(GistHistoryEntry instance) => }; GistComment _$GistCommentFromJson(Map json) => GistComment( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), user: json['user'] == null ? null : User.fromJson(json['user'] as Map), diff --git a/lib/src/common/model/git.g.dart b/lib/src/common/model/git.g.dart index 61d5fc44..ccbb082b 100644 --- a/lib/src/common/model/git.g.dart +++ b/lib/src/common/model/git.g.dart @@ -11,7 +11,7 @@ GitBlob _$GitBlobFromJson(Map json) => GitBlob( encoding: json['encoding'] as String?, url: json['url'] as String?, sha: json['sha'] as String?, - size: json['size'] as int?, + size: (json['size'] as num?)?.toInt(), ); Map _$GitBlobToJson(GitBlob instance) => { @@ -50,7 +50,7 @@ GitCommit _$GitCommitFromJson(Map json) => GitCommit( parents: (json['parents'] as List?) ?.map((e) => GitCommit.fromJson(e as Map)) .toList(), - commentCount: json['comment_count'] as int?, + commentCount: (json['comment_count'] as num?)?.toInt(), ); Map _$GitCommitToJson(GitCommit instance) => { @@ -95,20 +95,12 @@ GitCommitUser _$GitCommitUserFromJson(Map json) => json['date'] == null ? null : DateTime.parse(json['date'] as String), ); -Map _$GitCommitUserToJson(GitCommitUser instance) { - final val = {}; - - void writeNotNull(String key, dynamic value) { - if (value != null) { - val[key] = value; - } - } - - writeNotNull('name', instance.name); - writeNotNull('email', instance.email); - writeNotNull('date', dateToGitHubIso8601(instance.date)); - return val; -} +Map _$GitCommitUserToJson(GitCommitUser instance) => + { + if (instance.name case final value?) 'name': value, + if (instance.email case final value?) 'email': value, + if (dateToGitHubIso8601(instance.date) case final value?) 'date': value, + }; GitTree _$GitTreeFromJson(Map json) => GitTree( json['sha'] as String?, @@ -130,7 +122,7 @@ GitTreeEntry _$GitTreeEntryFromJson(Map json) => GitTreeEntry( json['path'] as String?, json['mode'] as String?, json['type'] as String?, - json['size'] as int?, + (json['size'] as num?)?.toInt(), json['sha'] as String?, json['url'] as String?, ); diff --git a/lib/src/common/model/issues.dart b/lib/src/common/model/issues.dart index 4534892e..3d531e63 100644 --- a/lib/src/common/model/issues.dart +++ b/lib/src/common/model/issues.dart @@ -1,5 +1,4 @@ import 'package:github/src/common.dart'; -import 'package:github/src/common/model/users.dart'; import 'package:json_annotation/json_annotation.dart'; part 'issues.g.dart'; @@ -26,6 +25,24 @@ class Issue { this.updatedAt, this.body = '', this.closedBy, + + // Properties from the Timeline API + this.activeLockReason, + this.authorAssociation, + this.bodyHtml, + this.bodyText, + this.commentsUrl, + this.draft, + this.eventsUrl, + this.labelsUrl, + this.locked, + this.nodeId, + this.performedViaGithubApp, + this.reactions, + this.repository, + this.repositoryUrl, + this.stateReason, + this.timelineUrl, }) { if (labels != null) { this.labels = labels; @@ -86,8 +103,48 @@ class Issue { /// The user who closed the issue User? closedBy; - bool get isOpen => state == 'open'; - bool get isClosed => state == 'closed'; + bool get isOpen => state.toUpperCase() == 'OPEN'; + bool get isClosed => state.toUpperCase() == 'CLOSED'; + + // The following properties were added to support the Timeline API. + + String? activeLockReason; + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + String? bodyHtml; + + String? bodyText; + + String? commentsUrl; + + bool? draft; + + String? eventsUrl; + + String? labelsUrl; + + bool? locked; + + String? nodeId; + + GitHubApp? performedViaGithubApp; + + ReactionRollup? reactions; + + Repository? repository; + + String? repositoryUrl; + + /// The reason for the current state + /// + /// Example: `not_planned` + String? stateReason; + + String? timelineUrl; factory Issue.fromJson(Map input) => _$IssueFromJson(input); Map toJson() => _$IssueToJson(this); @@ -172,12 +229,15 @@ class IssueLabel { IssueLabel({ this.name = '', this.color = '', + this.description = '', }); String name; String color; + String description; + factory IssueLabel.fromJson(Map input) => _$IssueLabelFromJson(input); Map toJson() => _$IssueLabelToJson(this); @@ -201,6 +261,13 @@ class Milestone { this.createdAt, this.updatedAt, this.dueOn, + + // Properties from the Timeline API + this.closedAt, + this.htmlUrl, + this.labelsUrl, + this.nodeId, + this.url, }); /// Unique Identifier for Milestone @@ -238,6 +305,22 @@ class Milestone { /// The due date for this milestone DateTime? dueOn; + // The following properties were added to support the Timeline API. + + DateTime? closedAt; + + /// Example: `https://github.com/octocat/Hello-World/milestones/v1.0` + String? htmlUrl; + + /// Example: `https://api.github.com/repos/octocat/Hello-World/milestones/1/labels` + String? labelsUrl; + + /// Example: `MDk6TWlsZXN0b25lMTAwMjYwNA==` + String? nodeId; + + /// Example: `https://api.github.com/repos/octocat/Hello-World/milestones/1` + String? url; + factory Milestone.fromJson(Map input) => _$MilestoneFromJson(input); Map toJson() => _$MilestoneToJson(this); diff --git a/lib/src/common/model/issues.g.dart b/lib/src/common/model/issues.g.dart index 12e03103..e4a80082 100644 --- a/lib/src/common/model/issues.g.dart +++ b/lib/src/common/model/issues.g.dart @@ -7,10 +7,10 @@ part of 'issues.dart'; // ************************************************************************** Issue _$IssueFromJson(Map json) => Issue( - id: json['id'] as int? ?? 0, + id: (json['id'] as num?)?.toInt() ?? 0, url: json['url'] as String? ?? '', htmlUrl: json['html_url'] as String? ?? '', - number: json['number'] as int? ?? 0, + number: (json['number'] as num?)?.toInt() ?? 0, state: json['state'] as String? ?? '', title: json['title'] as String? ?? '', user: json['user'] == null @@ -29,7 +29,7 @@ Issue _$IssueFromJson(Map json) => Issue( milestone: json['milestone'] == null ? null : Milestone.fromJson(json['milestone'] as Map), - commentsCount: json['comments'] as int? ?? 0, + commentsCount: (json['comments'] as num?)?.toInt() ?? 0, pullRequest: json['pull_request'] == null ? null : IssuePullRequest.fromJson( @@ -47,6 +47,29 @@ Issue _$IssueFromJson(Map json) => Issue( closedBy: json['closed_by'] == null ? null : User.fromJson(json['closed_by'] as Map), + activeLockReason: json['active_lock_reason'] as String?, + authorAssociation: json['author_association'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + commentsUrl: json['comments_url'] as String?, + draft: json['draft'] as bool?, + eventsUrl: json['events_url'] as String?, + labelsUrl: json['labels_url'] as String?, + locked: json['locked'] as bool?, + nodeId: json['node_id'] as String?, + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + repositoryUrl: json['repository_url'] as String?, + stateReason: json['state_reason'] as String?, + timelineUrl: json['timeline_url'] as String?, ); Map _$IssueToJson(Issue instance) => { @@ -68,6 +91,22 @@ Map _$IssueToJson(Issue instance) => { 'updated_at': instance.updatedAt?.toIso8601String(), 'body': instance.body, 'closed_by': instance.closedBy, + 'active_lock_reason': instance.activeLockReason, + 'author_association': instance.authorAssociation, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'comments_url': instance.commentsUrl, + 'draft': instance.draft, + 'events_url': instance.eventsUrl, + 'labels_url': instance.labelsUrl, + 'locked': instance.locked, + 'node_id': instance.nodeId, + 'performed_via_github_app': instance.performedViaGithubApp, + 'reactions': instance.reactions, + 'repository': instance.repository, + 'repository_url': instance.repositoryUrl, + 'state_reason': instance.stateReason, + 'timeline_url': instance.timelineUrl, }; IssueRequest _$IssueRequestFromJson(Map json) => IssueRequest( @@ -80,7 +119,7 @@ IssueRequest _$IssueRequestFromJson(Map json) => IssueRequest( ?.map((e) => e as String) .toList(), state: json['state'] as String?, - milestone: json['milestone'] as int?, + milestone: (json['milestone'] as num?)?.toInt(), ); Map _$IssueRequestToJson(IssueRequest instance) => @@ -109,7 +148,7 @@ Map _$IssuePullRequestToJson(IssuePullRequest instance) => }; IssueComment _$IssueCommentFromJson(Map json) => IssueComment( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), body: json['body'] as String?, user: json['user'] == null ? null @@ -142,25 +181,27 @@ Map _$IssueCommentToJson(IssueComment instance) => IssueLabel _$IssueLabelFromJson(Map json) => IssueLabel( name: json['name'] as String? ?? '', color: json['color'] as String? ?? '', + description: json['description'] as String? ?? '', ); Map _$IssueLabelToJson(IssueLabel instance) => { 'name': instance.name, 'color': instance.color, + 'description': instance.description, }; Milestone _$MilestoneFromJson(Map json) => Milestone( - id: json['id'] as int?, - number: json['number'] as int?, + id: (json['id'] as num?)?.toInt(), + number: (json['number'] as num?)?.toInt(), state: json['state'] as String?, title: json['title'] as String?, description: json['description'] as String?, creator: json['creator'] == null ? null : User.fromJson(json['creator'] as Map), - openIssuesCount: json['open_issues'] as int?, - closedIssuesCount: json['closed_issues'] as int?, + openIssuesCount: (json['open_issues'] as num?)?.toInt(), + closedIssuesCount: (json['closed_issues'] as num?)?.toInt(), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -170,6 +211,13 @@ Milestone _$MilestoneFromJson(Map json) => Milestone( dueOn: json['due_on'] == null ? null : DateTime.parse(json['due_on'] as String), + closedAt: json['closed_at'] == null + ? null + : DateTime.parse(json['closed_at'] as String), + htmlUrl: json['html_url'] as String?, + labelsUrl: json['labels_url'] as String?, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, ); Map _$MilestoneToJson(Milestone instance) => { @@ -184,6 +232,11 @@ Map _$MilestoneToJson(Milestone instance) => { 'created_at': instance.createdAt?.toIso8601String(), 'updated_at': instance.updatedAt?.toIso8601String(), 'due_on': instance.dueOn?.toIso8601String(), + 'closed_at': instance.closedAt?.toIso8601String(), + 'html_url': instance.htmlUrl, + 'labels_url': instance.labelsUrl, + 'node_id': instance.nodeId, + 'url': instance.url, }; CreateMilestone _$CreateMilestoneFromJson(Map json) => diff --git a/lib/src/common/model/keys.g.dart b/lib/src/common/model/keys.g.dart index 1db24c87..7cecbcf2 100644 --- a/lib/src/common/model/keys.g.dart +++ b/lib/src/common/model/keys.g.dart @@ -7,7 +7,7 @@ part of 'keys.dart'; // ************************************************************************** PublicKey _$PublicKeyFromJson(Map json) => PublicKey( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), key: json['key'] as String?, title: json['title'] as String?, ); diff --git a/lib/src/common/model/misc.dart b/lib/src/common/model/misc.dart index adf7b2be..18fc0a54 100644 --- a/lib/src/common/model/misc.dart +++ b/lib/src/common/model/misc.dart @@ -60,27 +60,65 @@ class RateLimit { Map toJson() => _$RateLimitToJson(this); } -/// Model class for the GitHub api status. +/// Model class for the GitHub API status. @JsonSerializable() class APIStatus { APIStatus({ + this.page, this.status, - this.lastUpdatedAt, - this.createdOn, - this.message, }); - final String? status; - @JsonKey(name: 'last_updated') - final DateTime? lastUpdatedAt; + /// Details about where to find more information. + final APIStatusPage? page; - @JsonKey(name: 'created_on') - final DateTime? createdOn; - - @JsonKey(name: 'body') - final String? message; + /// An overview of the current status. + final APIStatusMessage? status; factory APIStatus.fromJson(Map input) => _$APIStatusFromJson(input); Map toJson() => _$APIStatusToJson(this); } + +@JsonSerializable() +class APIStatusPage { + const APIStatusPage({ + this.id, + this.name, + this.url, + this.updatedAt, + }); + + /// Unique identifier for the current status. + final String? id; + + final String? name; + + /// Where to get more detailed information. + final String? url; + + @JsonKey(name: 'updated_at') + final DateTime? updatedAt; + + factory APIStatusPage.fromJson(Map input) => + _$APIStatusPageFromJson(input); + Map toJson() => _$APIStatusPageToJson(this); +} + +/// Overview class of the GitHub API status. +@JsonSerializable() +class APIStatusMessage { + const APIStatusMessage({ + this.description, + this.indicator, + }); + + /// A human description of the blended component status. + final String? description; + + /// An indicator - one of none, minor, major, or critical. + final String? indicator; + + factory APIStatusMessage.fromJson(Map input) => + _$APIStatusMessageFromJson(input); + Map toJson() => _$APIStatusMessageToJson(this); +} diff --git a/lib/src/common/model/misc.g.dart b/lib/src/common/model/misc.g.dart index 34362c50..4ad9a310 100644 --- a/lib/src/common/model/misc.g.dart +++ b/lib/src/common/model/misc.g.dart @@ -19,8 +19,8 @@ Map _$GitignoreTemplateToJson(GitignoreTemplate instance) => }; RateLimit _$RateLimitFromJson(Map json) => RateLimit( - json['limit'] as int?, - json['remaining'] as int?, + (json['limit'] as num?)?.toInt(), + (json['remaining'] as num?)?.toInt(), json['resets'] == null ? null : DateTime.parse(json['resets'] as String), ); @@ -31,19 +31,45 @@ Map _$RateLimitToJson(RateLimit instance) => { }; APIStatus _$APIStatusFromJson(Map json) => APIStatus( - status: json['status'] as String?, - lastUpdatedAt: json['last_updated'] == null + page: json['page'] == null ? null - : DateTime.parse(json['last_updated'] as String), - createdOn: json['created_on'] == null + : APIStatusPage.fromJson(json['page'] as Map), + status: json['status'] == null ? null - : DateTime.parse(json['created_on'] as String), - message: json['body'] as String?, + : APIStatusMessage.fromJson(json['status'] as Map), ); Map _$APIStatusToJson(APIStatus instance) => { + 'page': instance.page, 'status': instance.status, - 'last_updated': instance.lastUpdatedAt?.toIso8601String(), - 'created_on': instance.createdOn?.toIso8601String(), - 'body': instance.message, + }; + +APIStatusPage _$APIStatusPageFromJson(Map json) => + APIStatusPage( + id: json['id'] as String?, + name: json['name'] as String?, + url: json['url'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$APIStatusPageToJson(APIStatusPage instance) => + { + 'id': instance.id, + 'name': instance.name, + 'url': instance.url, + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +APIStatusMessage _$APIStatusMessageFromJson(Map json) => + APIStatusMessage( + description: json['description'] as String?, + indicator: json['indicator'] as String?, + ); + +Map _$APIStatusMessageToJson(APIStatusMessage instance) => + { + 'description': instance.description, + 'indicator': instance.indicator, }; diff --git a/lib/src/common/model/orgs.dart b/lib/src/common/model/orgs.dart index 21f7b417..5a26bb2c 100644 --- a/lib/src/common/model/orgs.dart +++ b/lib/src/common/model/orgs.dart @@ -24,57 +24,57 @@ class Organization { }); /// Organization Login - final String? login; + String? login; /// Organization ID - final int? id; + int? id; /// Url to Organization Profile @JsonKey(name: 'html_url') - final String? htmlUrl; + String? htmlUrl; /// Url to the Organization Avatar @JsonKey(name: 'avatar_url') - final String? avatarUrl; + String? avatarUrl; /// Organization Name - final String? name; + String? name; /// Organization Company - final String? company; + String? company; /// Organization Blog - final String? blog; + String? blog; /// Organization Location - final String? location; + String? location; /// Organization Email - final String? email; + String? email; /// Number of Public Repositories @JsonKey(name: 'public_repos') - final int? publicReposCount; + int? publicReposCount; /// Number of Public Gists @JsonKey(name: 'public_gists') - final int? publicGistsCount; + int? publicGistsCount; /// Number of Followers @JsonKey(name: 'followers') - final int? followersCount; + int? followersCount; /// Number of People this Organization is Following @JsonKey(name: 'following') - final int? followingCount; + int? followingCount; /// Time this organization was created @JsonKey(name: 'created_at') - final DateTime? createdAt; + DateTime? createdAt; /// Time this organization was updated @JsonKey(name: 'updated_at') - final DateTime? updatedAt; + DateTime? updatedAt; factory Organization.fromJson(Map input) => _$OrganizationFromJson(input); @@ -88,8 +88,9 @@ class OrganizationMembership { this.state, this.organization, }); - final String? state; - final Organization? organization; + + String? state; + Organization? organization; factory OrganizationMembership.fromJson(Map input) { return _$OrganizationMembershipFromJson(input); @@ -98,49 +99,139 @@ class OrganizationMembership { } /// Model class for a GitHub team. +/// +/// Different end-points populate different sets of properties. +/// +/// Groups of organization members that gives permissions on specified repositories. @JsonSerializable() class Team { Team({ - this.name, + this.description, + this.htmlUrl, this.id, - this.permission, + this.ldapDn, this.membersCount, - this.reposCount, + this.membersUrl, + this.name, + this.nodeId, this.organization, + this.parent, + this.permission, + this.permissions, + this.privacy, + this.reposCount, + this.repositoriesUrl, + this.slug, + this.url, }); - /// Team Name - final String? name; + /// Description of the team + /// + /// Example: `A great team.` + String? description; - /// Team ID - final int? id; + /// Format: uri + /// + /// Example: `https://github.com/orgs/rails/teams/core` + String? htmlUrl; - /// Team Permission - final String? permission; + /// Unique identifier of the team + /// + /// Example: `1` + int? id; + + /// Distinguished Name (DN) that team maps to within LDAP environment + /// + /// Example: `uid=example,ou=users,dc=github,dc=com` + String? ldapDn; /// Number of Members @JsonKey(name: 'members_count') - final int? membersCount; + int? membersCount; + + /// Example: `https://api.github.com/organizations/1/team/1/members{/member}` + String? membersUrl; + + /// Name of the team + /// + /// Example: `Justice League` + String? name; + + /// Example: `MDQ6VGVhbTE=` + String? nodeId; + + /// Organization + Organization? organization; + + /// Team Simple + /// + /// Groups of organization members that gives permissions on specified repositories. + Team? parent; + + /// Permission that the team will have for its repositories + /// + /// Example: `admin` + String? permission; + + Permissions? permissions; + + /// The level of privacy this team should have + /// + /// Example: `closed` + String? privacy; /// Number of Repositories @JsonKey(name: 'repos_count') - final int? reposCount; + int? reposCount; - /// Organization - final Organization? organization; + /// Format: uri + /// + /// Example: `https://api.github.com/organizations/1/team/1/repos` + String? repositoriesUrl; + + /// Example: `justice-league` + String? slug; + + /// URL for the team + /// + /// Format: uri + /// + /// Example: `https://api.github.com/organizations/1/team/1` + String? url; - factory Team.fromJson(Map input) { - return _$TeamFromJson(input); - } Map toJson() => _$TeamToJson(this); + + factory Team.fromJson(Map input) => _$TeamFromJson(input); +} + +@JsonSerializable() +class Permissions { + Permissions({ + this.admin, + this.maintain, + this.pull, + this.push, + this.triage, + }); + + bool? admin; + bool? maintain; + bool? pull; + bool? push; + bool? triage; + + Map toJson() => _$PermissionsToJson(this); + + factory Permissions.fromJson(Map input) => + _$PermissionsFromJson(input); } /// Model class for the team membership state. class TeamMembershipState { - final String? name; - TeamMembershipState(this.name); + String? name; + bool get isPending => name == 'pending'; bool get isActive => name == 'active'; bool get isInactive => name == null; @@ -158,25 +249,25 @@ class TeamMember { this.htmlUrl}); /// Member Username - final String? login; + String? login; /// Member ID - final int? id; + int? id; /// Url to Member Avatar @JsonKey(name: 'avatar_url') - final String? avatarUrl; + String? avatarUrl; /// Member Type - final String? type; + String? type; /// If the member is a site administrator @JsonKey(name: 'site_admin') - final bool? siteAdmin; + bool? siteAdmin; /// Profile of the Member @JsonKey(name: 'html_url') - final String? htmlUrl; + String? htmlUrl; factory TeamMember.fromJson(Map input) { return _$TeamMemberFromJson(input); diff --git a/lib/src/common/model/orgs.g.dart b/lib/src/common/model/orgs.g.dart index 0e19f2cf..dc9dc349 100644 --- a/lib/src/common/model/orgs.g.dart +++ b/lib/src/common/model/orgs.g.dart @@ -8,7 +8,7 @@ part of 'orgs.dart'; Organization _$OrganizationFromJson(Map json) => Organization( login: json['login'] as String?, - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), htmlUrl: json['html_url'] as String?, avatarUrl: json['avatar_url'] as String?, name: json['name'] as String?, @@ -16,10 +16,10 @@ Organization _$OrganizationFromJson(Map json) => Organization( blog: json['blog'] as String?, location: json['location'] as String?, email: json['email'] as String?, - publicReposCount: json['public_repos'] as int?, - publicGistsCount: json['public_gists'] as int?, - followersCount: json['followers'] as int?, - followingCount: json['following'] as int?, + publicReposCount: (json['public_repos'] as num?)?.toInt(), + publicGistsCount: (json['public_gists'] as num?)?.toInt(), + followersCount: (json['followers'] as num?)?.toInt(), + followingCount: (json['following'] as num?)?.toInt(), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -64,28 +64,71 @@ Map _$OrganizationMembershipToJson( }; Team _$TeamFromJson(Map json) => Team( + description: json['description'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + ldapDn: json['ldap_dn'] as String?, + membersCount: (json['members_count'] as num?)?.toInt(), + membersUrl: json['members_url'] as String?, name: json['name'] as String?, - id: json['id'] as int?, - permission: json['permission'] as String?, - membersCount: json['members_count'] as int?, - reposCount: json['repos_count'] as int?, + nodeId: json['node_id'] as String?, organization: json['organization'] == null ? null : Organization.fromJson(json['organization'] as Map), + parent: json['parent'] == null + ? null + : Team.fromJson(json['parent'] as Map), + permission: json['permission'] as String?, + permissions: json['permissions'] == null + ? null + : Permissions.fromJson(json['permissions'] as Map), + privacy: json['privacy'] as String?, + reposCount: (json['repos_count'] as num?)?.toInt(), + repositoriesUrl: json['repositories_url'] as String?, + slug: json['slug'] as String?, + url: json['url'] as String?, ); Map _$TeamToJson(Team instance) => { - 'name': instance.name, + 'description': instance.description, + 'html_url': instance.htmlUrl, 'id': instance.id, - 'permission': instance.permission, + 'ldap_dn': instance.ldapDn, 'members_count': instance.membersCount, - 'repos_count': instance.reposCount, + 'members_url': instance.membersUrl, + 'name': instance.name, + 'node_id': instance.nodeId, 'organization': instance.organization, + 'parent': instance.parent, + 'permission': instance.permission, + 'permissions': instance.permissions, + 'privacy': instance.privacy, + 'repos_count': instance.reposCount, + 'repositories_url': instance.repositoriesUrl, + 'slug': instance.slug, + 'url': instance.url, + }; + +Permissions _$PermissionsFromJson(Map json) => Permissions( + admin: json['admin'] as bool?, + maintain: json['maintain'] as bool?, + pull: json['pull'] as bool?, + push: json['push'] as bool?, + triage: json['triage'] as bool?, + ); + +Map _$PermissionsToJson(Permissions instance) => + { + 'admin': instance.admin, + 'maintain': instance.maintain, + 'pull': instance.pull, + 'push': instance.push, + 'triage': instance.triage, }; TeamMember _$TeamMemberFromJson(Map json) => TeamMember( login: json['login'] as String?, - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), avatarUrl: json['avatar_url'] as String?, type: json['type'] as String?, siteAdmin: json['site_admin'] as bool?, diff --git a/lib/src/common/model/pulls.dart b/lib/src/common/model/pulls.dart index fd6e8d70..2cb10a8e 100644 --- a/lib/src/common/model/pulls.dart +++ b/lib/src/common/model/pulls.dart @@ -9,6 +9,7 @@ part 'pulls.g.dart'; class PullRequest { PullRequest({ this.id, + this.nodeId, this.htmlUrl, this.diffUrl, this.patchUrl, @@ -46,6 +47,9 @@ class PullRequest { /// Pull Request ID int? id; + /// Unique node identification string. + String? nodeId; + /// Url to the Pull Request Page String? htmlUrl; @@ -90,6 +94,7 @@ class PullRequest { /// Whether or not the pull request is a draft bool? draft; + String? mergeCommitSha; /// If the pull request was merged @@ -157,6 +162,7 @@ class PullRequestMerge { this.sha, this.message, }); + bool? merged; String? sha; String? message; @@ -194,16 +200,16 @@ class CreatePullRequest { CreatePullRequest(this.title, this.head, this.base, {this.draft = false, this.body}); - final String? title; - final String? head; - final String? base; + String? title; + String? head; + String? base; /// Whether a draft PR should be created. /// /// This is currently experimental functionality since the way draft PRs are /// created through Github's REST API is in developer preview only - and could change at any time. @experimental - final bool? draft; + bool? draft; String? body; @@ -231,6 +237,7 @@ class PullRequestComment { this.pullRequestUrl, this.links, }); + int? id; String? diffHunk; String? path; @@ -280,6 +287,7 @@ class PullRequestFile { this.contentsUrl, this.patch, }); + String? sha; String? filename; String? status; @@ -303,13 +311,14 @@ class PullRequestFile { class PullRequestReview { PullRequestReview( {required this.id, - required this.user, + this.user, this.body, this.state, this.htmlUrl, this.pullRequestUrl}); + int id; - User user; + User? user; String? body; String? state; String? htmlUrl; @@ -326,17 +335,209 @@ class PullRequestReview { @JsonSerializable() class CreatePullRequestReview { CreatePullRequestReview(this.owner, this.repo, this.pullNumber, this.event, - {this.body}); + {this.body, this.comments}); String owner; String repo; String event; String? body; int pullNumber; - // TODO List comments; + List? comments; factory CreatePullRequestReview.fromJson(Map input) => _$CreatePullRequestReviewFromJson(input); Map toJson() => _$CreatePullRequestReviewToJson(this); } -// TODO PullRequestReviewComment https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request + +/// Pull Request Review Comment +/// +/// Pull Request Review Comments are comments on a portion of the Pull Request's +/// diff. +@JsonSerializable() +class PullRequestReviewComment { + PullRequestReviewComment({ + this.authorAssociation, + this.body, + this.bodyHtml, + this.bodyText, + this.commitId, + this.createdAt, + this.diffHunk, + this.htmlUrl, + this.id, + this.inReplyToId, + this.line, + this.links, + this.nodeId, + this.originalCommitId, + this.originalLine, + this.originalPosition, + this.originalStartLine, + this.path, + this.position, + this.pullRequestReviewId, + this.pullRequestUrl, + this.reactions, + this.side, + this.startLine, + this.startSide, + this.subjectType, + this.updatedAt, + this.url, + this.user, + }); + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + /// The text of the comment. + /// + /// Example: `We should probably include a check for null values here.` + String? body; + + /// Example: `"

comment body

"` + String? bodyHtml; + + /// Example: `"comment body"` + String? bodyText; + + /// The SHA of the commit to which the comment applies. + /// + /// Example: `6dcb09b5b57875f334f61aebed695e2e4193db5e` + String? commitId; + + DateTime? createdAt; + + /// The diff of the line that the comment refers to. + /// + /// Example: `@@ -16,33 +16,40 @@ public class Connection : IConnection...` + String? diffHunk; + + /// HTML URL for the pull request review comment. + /// + /// Example: `https://github.com/octocat/Hello-World/pull/1#discussion-diff-1` + String? htmlUrl; + + /// The ID of the pull request review comment. + int? id; + + /// The comment ID to reply to. + int? inReplyToId; + + /// The line of the blob to which the comment applies. The last line of the range + /// for a multi-line comment + int? line; + + @JsonKey(name: '_links') + ReviewLinks? links; + + /// The node ID of the pull request review comment. + /// + /// Example: `MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDEw` + String? nodeId; + + /// The SHA of the original commit to which the comment applies. + /// + /// Example: `9c48853fa3dc5c1c3d6f1f1cd1f2743e72652840` + String? originalCommitId; + + /// The line of the blob to which the comment applies. The last line of the range + /// for a multi-line comment + int? originalLine; + + /// The index of the original line in the diff to which the comment applies. This + /// field is deprecated; use `original_line` instead. + int? originalPosition; + + /// The first line of the range for a multi-line comment. + int? originalStartLine; + + /// The relative path of the file to which the comment applies. + /// + /// Example: `config/database.yaml` + String? path; + + /// The line index in the diff to which the comment applies. This field is deprecated; + /// use `line` instead. + int? position; + + /// The ID of the pull request review to which the comment belongs. + int? pullRequestReviewId; + + /// URL for the pull request that the review comment belongs to. + /// + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/1` + String? pullRequestUrl; + + /// Reaction Rollup + ReactionRollup? reactions; + + /// The side of the diff to which the comment applies. The side of the last line + /// of the range for a multi-line comment + String? side; + + /// The first line of the range for a multi-line comment. + int? startLine; + + /// The side of the first line of the range for a multi-line comment. + String? startSide; + + /// The level at which the comment is targeted, can be a diff line or a file. + String? subjectType; + + DateTime? updatedAt; + + /// URL for the pull request review comment + /// + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/comments/1` + String? url; + + User? user; + + Map toJson() => _$PullRequestReviewCommentToJson(this); + + factory PullRequestReviewComment.fromJson(Map input) => + _$PullRequestReviewCommentFromJson(input); +} + +/// This is similar to [Links] but represents a different serialization +/// in the GitHub API. +/// +/// It is used for [PullRequestReviewComment.links] and +/// [ReviewEvent.links]. +class ReviewLinks { + ReviewLinks({ + this.html, + this.pullRequest, + this.self, + }); + + Uri? html; + Uri? pullRequest; + Uri? self; + + Map toJson() { + return { + 'html': {'href': html?.toString()}, + 'pullRequest': {'href': pullRequest?.toString()}, + 'self': {'href': self?.toString()}, + }; + } + + static Uri? _parseBlock(Map input, String key) { + if (input[key] is Map && input[key]['href'] is String) { + return Uri.parse(input[key]['href']! as String); + } + return null; + } + + factory ReviewLinks.fromJson(Map input) { + return ReviewLinks( + html: _parseBlock(input, 'html'), + pullRequest: _parseBlock(input, 'pull_request'), + self: _parseBlock(input, 'self'), + ); + } +} diff --git a/lib/src/common/model/pulls.g.dart b/lib/src/common/model/pulls.g.dart index 62d2bba6..e7dea0cb 100644 --- a/lib/src/common/model/pulls.g.dart +++ b/lib/src/common/model/pulls.g.dart @@ -7,11 +7,12 @@ part of 'pulls.dart'; // ************************************************************************** PullRequest _$PullRequestFromJson(Map json) => PullRequest( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), + nodeId: json['node_id'] as String?, htmlUrl: json['html_url'] as String?, diffUrl: json['diff_url'] as String?, patchUrl: json['patch_url'] as String?, - number: json['number'] as int?, + number: (json['number'] as num?)?.toInt(), state: json['state'] as String?, title: json['title'] as String?, body: json['body'] as String?, @@ -43,18 +44,18 @@ PullRequest _$PullRequestFromJson(Map json) => PullRequest( mergedBy: json['merged_by'] == null ? null : User.fromJson(json['merged_by'] as Map), - commentsCount: json['comments'] as int? ?? 0, - commitsCount: json['commits'] as int? ?? 0, - additionsCount: json['additions'] as int? ?? 0, - deletionsCount: json['deletions'] as int? ?? 0, - changedFilesCount: json['changed_files'] as int? ?? 0, + commentsCount: (json['comments'] as num?)?.toInt() ?? 0, + commitsCount: (json['commits'] as num?)?.toInt() ?? 0, + additionsCount: (json['additions'] as num?)?.toInt() ?? 0, + deletionsCount: (json['deletions'] as num?)?.toInt() ?? 0, + changedFilesCount: (json['changed_files'] as num?)?.toInt() ?? 0, labels: (json['labels'] as List?) ?.map((e) => IssueLabel.fromJson(e as Map)) .toList(), requestedReviewers: (json['requested_reviewers'] as List?) ?.map((e) => User.fromJson(e as Map)) .toList(), - reviewCommentCount: json['review_comments'] as int? ?? 0, + reviewCommentCount: (json['review_comments'] as num?)?.toInt() ?? 0, milestone: json['milestone'] == null ? null : Milestone.fromJson(json['milestone'] as Map), @@ -69,6 +70,7 @@ PullRequest _$PullRequestFromJson(Map json) => PullRequest( Map _$PullRequestToJson(PullRequest instance) => { 'id': instance.id, + 'node_id': instance.nodeId, 'html_url': instance.htmlUrl, 'diff_url': instance.diffUrl, 'patch_url': instance.patchUrl, @@ -160,11 +162,11 @@ Map _$CreatePullRequestToJson(CreatePullRequest instance) => PullRequestComment _$PullRequestCommentFromJson(Map json) => PullRequestComment( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), diffHunk: json['diff_hunk'] as String?, path: json['path'] as String?, - position: json['position'] as int?, - originalPosition: json['original_position'] as int?, + position: (json['position'] as num?)?.toInt(), + originalPosition: (json['original_position'] as num?)?.toInt(), commitId: json['commit_id'] as String?, originalCommitId: json['original_commit_id'] as String?, user: json['user'] == null @@ -208,7 +210,7 @@ CreatePullRequestComment _$CreatePullRequestCommentFromJson( json['body'] as String?, json['commit_id'] as String?, json['path'] as String?, - json['position'] as int?, + (json['position'] as num?)?.toInt(), ); Map _$CreatePullRequestCommentToJson( @@ -225,9 +227,9 @@ PullRequestFile _$PullRequestFileFromJson(Map json) => sha: json['sha'] as String?, filename: json['filename'] as String?, status: json['status'] as String?, - additionsCount: json['additions'] as int?, - deletionsCount: json['deletions'] as int?, - changesCount: json['changes'] as int?, + additionsCount: (json['additions'] as num?)?.toInt(), + deletionsCount: (json['deletions'] as num?)?.toInt(), + changesCount: (json['changes'] as num?)?.toInt(), blobUrl: json['blob_url'] as String?, rawUrl: json['raw_url'] as String?, contentsUrl: json['contents_url'] as String?, @@ -250,8 +252,10 @@ Map _$PullRequestFileToJson(PullRequestFile instance) => PullRequestReview _$PullRequestReviewFromJson(Map json) => PullRequestReview( - id: json['id'] as int, - user: User.fromJson(json['user'] as Map), + id: (json['id'] as num).toInt(), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), body: json['body'] as String?, state: json['state'] as String?, htmlUrl: json['html_url'] as String?, @@ -281,9 +285,13 @@ CreatePullRequestReview _$CreatePullRequestReviewFromJson( CreatePullRequestReview( json['owner'] as String, json['repo'] as String, - json['pull_number'] as int, + (json['pull_number'] as num).toInt(), json['event'] as String, body: json['body'] as String?, + comments: (json['comments'] as List?) + ?.map((e) => + PullRequestReviewComment.fromJson(e as Map)) + .toList(), ); Map _$CreatePullRequestReviewToJson( @@ -294,4 +302,83 @@ Map _$CreatePullRequestReviewToJson( 'event': instance.event, 'body': instance.body, 'pull_number': instance.pullNumber, + 'comments': instance.comments, + }; + +PullRequestReviewComment _$PullRequestReviewCommentFromJson( + Map json) => + PullRequestReviewComment( + authorAssociation: json['author_association'] as String?, + body: json['body'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + commitId: json['commit_id'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + diffHunk: json['diff_hunk'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + inReplyToId: (json['in_reply_to_id'] as num?)?.toInt(), + line: (json['line'] as num?)?.toInt(), + links: json['_links'] == null + ? null + : ReviewLinks.fromJson(json['_links'] as Map), + nodeId: json['node_id'] as String?, + originalCommitId: json['original_commit_id'] as String?, + originalLine: (json['original_line'] as num?)?.toInt(), + originalPosition: (json['original_position'] as num?)?.toInt(), + originalStartLine: (json['original_start_line'] as num?)?.toInt(), + path: json['path'] as String?, + position: (json['position'] as num?)?.toInt(), + pullRequestReviewId: (json['pull_request_review_id'] as num?)?.toInt(), + pullRequestUrl: json['pull_request_url'] as String?, + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + side: json['side'] as String?, + startLine: (json['start_line'] as num?)?.toInt(), + startSide: json['start_side'] as String?, + subjectType: json['subject_type'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + url: json['url'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$PullRequestReviewCommentToJson( + PullRequestReviewComment instance) => + { + 'author_association': instance.authorAssociation, + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'commit_id': instance.commitId, + 'created_at': instance.createdAt?.toIso8601String(), + 'diff_hunk': instance.diffHunk, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'in_reply_to_id': instance.inReplyToId, + 'line': instance.line, + '_links': instance.links, + 'node_id': instance.nodeId, + 'original_commit_id': instance.originalCommitId, + 'original_line': instance.originalLine, + 'original_position': instance.originalPosition, + 'original_start_line': instance.originalStartLine, + 'path': instance.path, + 'position': instance.position, + 'pull_request_review_id': instance.pullRequestReviewId, + 'pull_request_url': instance.pullRequestUrl, + 'reactions': instance.reactions, + 'side': instance.side, + 'start_line': instance.startLine, + 'start_side': instance.startSide, + 'subject_type': instance.subjectType, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'url': instance.url, + 'user': instance.user, }; diff --git a/lib/src/common/model/reaction.dart b/lib/src/common/model/reaction.dart index 00b8e4ac..a9b73d7f 100644 --- a/lib/src/common/model/reaction.dart +++ b/lib/src/common/model/reaction.dart @@ -10,12 +10,6 @@ part 'reaction.g.dart'; /// See https://developer.github.com/v3/reactions/ @JsonSerializable() class Reaction { - final int? id; - final String? nodeId; - final User? user; - final String? content; - final DateTime? createdAt; - Reaction({ this.id, this.nodeId, @@ -24,6 +18,12 @@ class Reaction { this.createdAt, }); + int? id; + String? nodeId; + User? user; + String? content; + DateTime? createdAt; + ReactionType? get type => ReactionType.fromString(content); factory Reaction.fromJson(Map json) => @@ -34,9 +34,10 @@ class Reaction { @immutable class ReactionType { + const ReactionType._(this.content, this.emoji); + final String content; final String emoji; - const ReactionType._(this.content, this.emoji); @override String toString() => content; @@ -71,3 +72,37 @@ class ReactionType { static ReactionType? fromString(String? content) => _types[content!]; } + +@JsonSerializable() +class ReactionRollup { + ReactionRollup({ + this.plusOne, + this.minusOne, + this.confused, + this.eyes, + this.heart, + this.hooray, + this.laugh, + this.rocket, + this.totalCount, + this.url, + }); + + @JsonKey(name: '+1') + int? plusOne; + @JsonKey(name: '-1') + int? minusOne; + int? confused; + int? eyes; + int? heart; + int? hooray; + int? laugh; + int? rocket; + int? totalCount; + String? url; + + Map toJson() => _$ReactionRollupToJson(this); + + factory ReactionRollup.fromJson(Map input) => + _$ReactionRollupFromJson(input); +} diff --git a/lib/src/common/model/reaction.g.dart b/lib/src/common/model/reaction.g.dart index dc2ff705..4647e160 100644 --- a/lib/src/common/model/reaction.g.dart +++ b/lib/src/common/model/reaction.g.dart @@ -7,7 +7,7 @@ part of 'reaction.dart'; // ************************************************************************** Reaction _$ReactionFromJson(Map json) => Reaction( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), nodeId: json['node_id'] as String?, user: json['user'] == null ? null @@ -25,3 +25,31 @@ Map _$ReactionToJson(Reaction instance) => { 'content': instance.content, 'created_at': instance.createdAt?.toIso8601String(), }; + +ReactionRollup _$ReactionRollupFromJson(Map json) => + ReactionRollup( + plusOne: (json['+1'] as num?)?.toInt(), + minusOne: (json['-1'] as num?)?.toInt(), + confused: (json['confused'] as num?)?.toInt(), + eyes: (json['eyes'] as num?)?.toInt(), + heart: (json['heart'] as num?)?.toInt(), + hooray: (json['hooray'] as num?)?.toInt(), + laugh: (json['laugh'] as num?)?.toInt(), + rocket: (json['rocket'] as num?)?.toInt(), + totalCount: (json['total_count'] as num?)?.toInt(), + url: json['url'] as String?, + ); + +Map _$ReactionRollupToJson(ReactionRollup instance) => + { + '+1': instance.plusOne, + '-1': instance.minusOne, + 'confused': instance.confused, + 'eyes': instance.eyes, + 'heart': instance.heart, + 'hooray': instance.hooray, + 'laugh': instance.laugh, + 'rocket': instance.rocket, + 'total_count': instance.totalCount, + 'url': instance.url, + }; diff --git a/lib/src/common/model/repos.dart b/lib/src/common/model/repos.dart index 3612aa34..d09c1a7e 100644 --- a/lib/src/common/model/repos.dart +++ b/lib/src/common/model/repos.dart @@ -5,17 +5,17 @@ part 'repos.g.dart'; @JsonSerializable() class GitHubComparison { - final String? url; - final String? status; - final int? aheadBy; - final int? behindBy; - final int? totalCommits; - final List? files; - final List? commits; - GitHubComparison(this.url, this.status, this.aheadBy, this.behindBy, this.totalCommits, this.files, this.commits); + String? url; + String? status; + int? aheadBy; + int? behindBy; + int? totalCommits; + List? files; + List? commits; + factory GitHubComparison.fromJson(Map json) => _$GitHubComparisonFromJson(json); Map toJson() => _$GitHubComparisonToJson(this); @@ -40,135 +40,416 @@ class GitHubComparison { /// Model class for a repository. @JsonSerializable() class Repository { - Repository( - {this.name = '', - this.id = 0, - this.fullName = '', - this.owner, - this.htmlUrl = '', - this.description = '', - this.cloneUrl = '', - this.gitUrl = '', - this.sshUrl = '', - this.svnUrl = '', - this.defaultBranch = '', - this.createdAt, - this.isPrivate = false, - this.isFork = false, - this.stargazersCount = 0, - this.watchersCount = 0, - this.language = '', - this.hasWiki = false, - this.hasDownloads = false, - this.forksCount = 0, - this.openIssuesCount = 0, - this.subscribersCount = 0, - this.networkCount = 0, - this.hasIssues = false, - this.size = 0, - this.archived = false, - this.disabled = false, - this.homepage = '', - this.updatedAt, - this.pushedAt, - this.license, - this.hasPages = false, - this.permissions}); + Repository({ + this.name = '', + this.id = 0, + this.fullName = '', + this.owner, + this.htmlUrl = '', + this.description = '', + this.cloneUrl = '', + this.gitUrl = '', + this.sshUrl = '', + this.svnUrl = '', + this.defaultBranch = '', + this.createdAt, + this.isPrivate = false, + this.isFork = false, + this.stargazersCount = 0, + this.watchersCount = 0, + this.language = '', + this.hasWiki = false, + this.hasDownloads = false, + this.forksCount = 0, + this.openIssuesCount = 0, + this.subscribersCount = 0, + this.networkCount = 0, + this.hasIssues = false, + this.size = 0, + this.archived = false, + this.disabled = false, + this.homepage = '', + this.updatedAt, + this.pushedAt, + this.license, + this.hasPages = false, + this.permissions, + + // Properties from the Timeline API + this.allowAutoMerge, + this.allowForking, + this.allowMergeCommit, + this.allowRebaseMerge, + this.allowSquashMerge, + this.allowUpdateBranch, + this.anonymousAccessEnabled, + this.archiveUrl, + this.assigneesUrl, + this.blobsUrl, + this.branchesUrl, + this.collaboratorsUrl, + this.commentsUrl, + this.commitsUrl, + this.compareUrl, + this.contentsUrl, + this.contributorsUrl, + this.deleteBranchOnMerge, + this.deploymentsUrl, + this.downloadsUrl, + this.eventsUrl, + this.forks, + this.forksUrl, + this.gitCommitsUrl, + this.gitRefsUrl, + this.gitTagsUrl, + this.hasDiscussions, + this.hasProjects, + this.hooksUrl, + this.isTemplate, + this.issueCommentUrl, + this.issueEventsUrl, + this.issuesUrl, + this.keysUrl, + this.labelsUrl, + this.languagesUrl, + this.masterBranch, + this.mergeCommitMessage, + this.mergeCommitTitle, + this.mergesUrl, + this.milestonesUrl, + this.mirrorUrl, + this.nodeId, + this.notificationsUrl, + this.openIssues, + this.organization, + this.pullsUrl, + this.releasesUrl, + this.squashMergeCommitMessage, + this.squashMergeCommitTitle, + this.stargazersUrl, + this.starredAt, + this.statusesUrl, + this.subscribersUrl, + this.subscriptionUrl, + this.tagsUrl, + this.teamsUrl, + this.tempCloneToken, + this.templateRepository, + this.topics, + this.treesUrl, + this.url, + this.visibility, + this.watchers, + this.webCommitSignoffRequired, + }); /// Repository Name - final String name; + String name; /// Repository ID - final int id; + int id; /// Full Repository Name - final String fullName; + String fullName; /// Repository Owner @JsonKey(defaultValue: null) - final UserInformation? owner; + UserInformation? owner; /// If the Repository is Private @JsonKey(name: 'private') - final bool isPrivate; + bool isPrivate; /// If the Repository is a fork @JsonKey(name: 'fork') - final bool isFork; + bool isFork; /// Url to the GitHub Repository Page - final String htmlUrl; + String htmlUrl; /// Repository Description - final String description; + String description; // https clone URL - final String cloneUrl; + String cloneUrl; - final String sshUrl; + String sshUrl; - final String svnUrl; + String svnUrl; - final String gitUrl; + String gitUrl; /// Url to the Repository Homepage - final String homepage; + String homepage; /// Repository Size - final int size; + // + /// The size of the repository. Size is calculated hourly. When a repository is + /// initially created, the size is 0. + int size; /// Repository Stars - final int stargazersCount; + int stargazersCount; /// Repository Watchers - final int watchersCount; + int watchersCount; /// Repository Language - final String language; + String language; /// If the Repository has Issues Enabled - final bool hasIssues; + bool hasIssues; /// If the Repository has the Wiki Enabled - final bool hasWiki; + bool hasWiki; /// If the Repository has any Downloads - final bool hasDownloads; + bool hasDownloads; /// If the Repository has any Github Pages - final bool hasPages; + bool hasPages; /// Number of Forks - final int forksCount; + int forksCount; /// Number of Open Issues - final int openIssuesCount; + int openIssuesCount; /// Repository Default Branch - final String defaultBranch; + String defaultBranch; /// Number of Subscribers - final int subscribersCount; + int subscribersCount; /// Number of users in the network - final int networkCount; + int networkCount; /// The time the repository was created at - final DateTime? createdAt; + DateTime? createdAt; /// The last time the repository was pushed at - final DateTime? pushedAt; + DateTime? pushedAt; - final DateTime? updatedAt; + DateTime? updatedAt; - final LicenseKind? license; + LicenseKind? license; - final bool archived; + bool archived; - final bool disabled; + bool disabled; RepositoryPermissions? permissions; + // The following properties were added to support the Timeline API. + + /// Whether to allow Auto-merge to be used on pull requests. + bool? allowAutoMerge; + + /// Whether to allow forking this repo + bool? allowForking; + + /// Whether to allow merge commits for pull requests. + bool? allowMergeCommit; + + /// Whether to allow rebase merges for pull requests. + bool? allowRebaseMerge; + + /// Whether to allow squash merges for pull requests. + bool? allowSquashMerge; + + /// Whether or not a pull request head branch that is behind its base branch can + /// always be updated even if it is not required to be up to date before merging. + bool? allowUpdateBranch; + + /// Whether anonymous git access is enabled for this repository + bool? anonymousAccessEnabled; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}` + String? archiveUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/assignees{/user}` + String? assigneesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}` + String? blobsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/branches{/branch}` + String? branchesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}` + String? collaboratorsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/comments{/number}` + String? commentsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/commits{/sha}` + String? commitsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}` + String? compareUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/contents/{+path}` + String? contentsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/contributors` + String? contributorsUrl; + + /// Whether to delete head branches when pull requests are merged + bool? deleteBranchOnMerge; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/deployments` + String? deploymentsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/downloads` + String? downloadsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/events` + String? eventsUrl; + + int? forks; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/forks` + String? forksUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/commits{/sha}` + String? gitCommitsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/refs{/sha}` + String? gitRefsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/tags{/sha}` + String? gitTagsUrl; + + /// Whether discussions are enabled. + bool? hasDiscussions; + + /// Whether projects are enabled. + bool? hasProjects; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/hooks` + String? hooksUrl; + + /// Whether this repository acts as a template that can be used to generate new + /// repositories. + bool? isTemplate; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/issues/comments{/number}` + String? issueCommentUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/issues/events{/number}` + String? issueEventsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/issues{/number}` + String? issuesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/keys{/key_id}` + String? keysUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/labels{/name}` + String? labelsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/languages` + String? languagesUrl; + + String? masterBranch; + + /// The default value for a merge commit message. + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `PR_BODY` - default to the pull request's body. + /// - `BLANK` - default to a blank commit message. + String? mergeCommitMessage; + + /// The default value for a merge commit title. + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., + /// Merge pull request #123 from branch-name). + String? mergeCommitTitle; + + /// Format: uri + /// + /// Example: `http://api.github.com/repos/octocat/Hello-World/merges` + String? mergesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/milestones{/number}` + String? milestonesUrl; + + /// Format: uri + /// + /// Example: `git:git.example.com/octocat/Hello-World` + String? mirrorUrl; + + /// Example: `MDEwOlJlcG9zaXRvcnkxMjk2MjY5` + String? nodeId; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}` + String? notificationsUrl; + + int? openIssues; + + User? organization; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/pulls{/number}` + String? pullsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/releases{/id}` + String? releasesUrl; + + /// The default value for a squash merge commit message: + /// + /// - `PR_BODY` - default to the pull request's body. + /// - `COMMIT_MESSAGES` - default to the branch's commit messages. + /// - `BLANK` - default to a blank commit message. + String? squashMergeCommitMessage; + + /// The default value for a squash merge commit title: + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) + /// or the pull request's title (when more than one commit). + String? squashMergeCommitTitle; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/stargazers` + String? stargazersUrl; + + DateTime? starredAt; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/statuses/{sha}` + String? statusesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/subscribers` + String? subscribersUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/subscription` + String? subscriptionUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/tags` + String? tagsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/teams` + String? teamsUrl; + + String? tempCloneToken; + + TemplateRepository? templateRepository; + + List? topics; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/trees{/sha}` + String? treesUrl; + + /// Example: `https://api.github.com/repos/octocat/Hello-World` + String? url; + + /// The repository visibility: public, private, or internal. + String? visibility; + + int? watchers; + + /// Whether to require contributors to sign off on web-based commits + bool? webCommitSignoffRequired; + factory Repository.fromJson(Map input) => _$RepositoryFromJson(input); Map toJson() => _$RepositoryToJson(this); @@ -187,13 +468,13 @@ class RepositoryPermissions { {this.admin = false, this.push = false, this.pull = false}); /// Administrative Access - final bool admin; + bool admin; /// Push Access - final bool push; + bool push; /// Pull Access - final bool pull; + bool pull; factory RepositoryPermissions.fromJson(Map json) => _$RepositoryPermissionsFromJson(json); @@ -203,14 +484,14 @@ class RepositoryPermissions { @JsonSerializable() class Tag { - final String name; - final CommitInfo commit; + Tag(this.name, this.commit, this.zipUrl, this.tarUrl); + + String name; + CommitInfo commit; @JsonKey(name: 'zipball_url') - final String zipUrl; + String zipUrl; @JsonKey(name: 'tarball_url') - final String tarUrl; - - Tag(this.name, this.commit, this.zipUrl, this.tarUrl); + String tarUrl; factory Tag.fromJson(Map input) => _$TagFromJson(input); Map toJson() => _$TagToJson(this); @@ -221,18 +502,18 @@ class Tag { @JsonSerializable() class CommitData { - final String? sha; - final GitCommit? commit; - final String? url; - final String? htmlUrl; - final String? commentsUrl; - - final CommitDataUser? author, committer; - final List>? parents; - CommitData(this.sha, this.commit, this.url, this.htmlUrl, this.commentsUrl, this.author, this.committer, this.parents); + String? sha; + GitCommit? commit; + String? url; + String? htmlUrl; + String? commentsUrl; + + CommitDataUser? author, committer; + List>? parents; + factory CommitData.fromJson(Map input) => _$CommitDataFromJson(input); Map toJson() => _$CommitDataToJson(this); @@ -240,11 +521,11 @@ class CommitData { @JsonSerializable() class CommitDataUser { - final String? login, type; + CommitDataUser(this.login, this.id, this.type); - final int? id; + String? login, type; - CommitDataUser(this.login, this.id, this.type); + int? id; factory CommitDataUser.fromJson(Map input) => _$CommitDataUserFromJson(input); @@ -253,11 +534,11 @@ class CommitDataUser { @JsonSerializable() class CommitInfo { - final String? sha; - final GitTree? tree; - CommitInfo(this.sha, this.tree); + String? sha; + GitTree? tree; + factory CommitInfo.fromJson(Map input) => _$CommitInfoFromJson(input); Map toJson() => _$CommitInfoToJson(this); @@ -266,19 +547,19 @@ class CommitInfo { /// User Information @JsonSerializable() class UserInformation { + UserInformation(this.login, this.id, this.avatarUrl, this.htmlUrl); + /// Owner Username - final String login; + String login; /// Owner ID - final int id; + int id; /// Avatar Url - final String avatarUrl; + String avatarUrl; /// Url to the user's GitHub Profile - final String htmlUrl; - - UserInformation(this.login, this.id, this.avatarUrl, this.htmlUrl); + String htmlUrl; factory UserInformation.fromJson(Map input) => _$UserInformationFromJson(input); @@ -288,14 +569,14 @@ class UserInformation { /// A Repository Slug @JsonSerializable() class RepositorySlug { + RepositorySlug(this.owner, this.name); + /// Repository Owner String owner; /// Repository Name String name; - RepositorySlug(this.owner, this.name); - /// Creates a Repository Slug from a full name. factory RepositorySlug.full(String f) { final split = f.split('/'); @@ -340,7 +621,7 @@ class CreateRepository { this.hasWiki}); /// Repository Name - final String? name; + String? name; /// Repository Description String? description; @@ -383,13 +664,13 @@ class CreateRepository { /// Model class for a branch. @JsonSerializable() class Branch { + Branch(this.name, this.commit); + /// The name of the branch. - final String? name; + String? name; /// Commit Information - final CommitData? commit; - - Branch(this.name, this.commit); + CommitData? commit; factory Branch.fromJson(Map json) => _$BranchFromJson(json); Map toJson() => _$BranchToJson(this); @@ -397,10 +678,10 @@ class Branch { /// A Breakdown of the Languages a repository uses. class LanguageBreakdown { - final Map _data; - LanguageBreakdown(Map data) : _data = data; + final Map _data; + /// The Primary Language String get primary { final list = mapToList(_data); @@ -439,25 +720,6 @@ class LanguageBreakdown { @JsonSerializable() class LicenseDetails { - final String? name; - final String? path; - final String? sha; - final int? size; - final Uri? url; - - final Uri? htmlUrl; - final Uri? gitUrl; - final Uri? downloadUrl; - - final String? type; - final String? content; - final String? encoding; - - @JsonKey(name: '_links') - final Links? links; - - final LicenseKind? license; - LicenseDetails( {this.name, this.path, @@ -473,6 +735,25 @@ class LicenseDetails { this.links, this.license}); + String? name; + String? path; + String? sha; + int? size; + Uri? url; + + Uri? htmlUrl; + Uri? gitUrl; + Uri? downloadUrl; + + String? type; + String? content; + String? encoding; + + @JsonKey(name: '_links') + Links? links; + + LicenseKind? license; + factory LicenseDetails.fromJson(Map json) => _$LicenseDetailsFromJson(json); @@ -481,14 +762,14 @@ class LicenseDetails { @JsonSerializable() class LicenseKind { - final String? key; - final String? name; - final String? spdxId; - final Uri? url; - final String? nodeId; - LicenseKind({this.key, this.name, this.spdxId, this.url, this.nodeId}); + String? key; + String? name; + String? spdxId; + Uri? url; + String? nodeId; + factory LicenseKind.fromJson(Map json) => _$LicenseKindFromJson(json); diff --git a/lib/src/common/model/repos.g.dart b/lib/src/common/model/repos.g.dart index b2e395bd..fe19ea97 100644 --- a/lib/src/common/model/repos.g.dart +++ b/lib/src/common/model/repos.g.dart @@ -10,9 +10,9 @@ GitHubComparison _$GitHubComparisonFromJson(Map json) => GitHubComparison( json['url'] as String?, json['status'] as String?, - json['ahead_by'] as int?, - json['behind_by'] as int?, - json['total_commits'] as int?, + (json['ahead_by'] as num?)?.toInt(), + (json['behind_by'] as num?)?.toInt(), + (json['total_commits'] as num?)?.toInt(), (json['files'] as List?) ?.map((e) => CommitFile.fromJson(e as Map)) .toList(), @@ -34,7 +34,7 @@ Map _$GitHubComparisonToJson(GitHubComparison instance) => Repository _$RepositoryFromJson(Map json) => Repository( name: json['name'] as String? ?? '', - id: json['id'] as int? ?? 0, + id: (json['id'] as num?)?.toInt() ?? 0, fullName: json['full_name'] as String? ?? '', owner: json['owner'] == null ? null @@ -51,17 +51,17 @@ Repository _$RepositoryFromJson(Map json) => Repository( : DateTime.parse(json['created_at'] as String), isPrivate: json['private'] as bool? ?? false, isFork: json['fork'] as bool? ?? false, - stargazersCount: json['stargazers_count'] as int? ?? 0, - watchersCount: json['watchers_count'] as int? ?? 0, + stargazersCount: (json['stargazers_count'] as num?)?.toInt() ?? 0, + watchersCount: (json['watchers_count'] as num?)?.toInt() ?? 0, language: json['language'] as String? ?? '', hasWiki: json['has_wiki'] as bool? ?? false, hasDownloads: json['has_downloads'] as bool? ?? false, - forksCount: json['forks_count'] as int? ?? 0, - openIssuesCount: json['open_issues_count'] as int? ?? 0, - subscribersCount: json['subscribers_count'] as int? ?? 0, - networkCount: json['network_count'] as int? ?? 0, + forksCount: (json['forks_count'] as num?)?.toInt() ?? 0, + openIssuesCount: (json['open_issues_count'] as num?)?.toInt() ?? 0, + subscribersCount: (json['subscribers_count'] as num?)?.toInt() ?? 0, + networkCount: (json['network_count'] as num?)?.toInt() ?? 0, hasIssues: json['has_issues'] as bool? ?? false, - size: json['size'] as int? ?? 0, + size: (json['size'] as num?)?.toInt() ?? 0, archived: json['archived'] as bool? ?? false, disabled: json['disabled'] as bool? ?? false, homepage: json['homepage'] as String? ?? '', @@ -79,6 +79,79 @@ Repository _$RepositoryFromJson(Map json) => Repository( ? null : RepositoryPermissions.fromJson( json['permissions'] as Map), + allowAutoMerge: json['allow_auto_merge'] as bool?, + allowForking: json['allow_forking'] as bool?, + allowMergeCommit: json['allow_merge_commit'] as bool?, + allowRebaseMerge: json['allow_rebase_merge'] as bool?, + allowSquashMerge: json['allow_squash_merge'] as bool?, + allowUpdateBranch: json['allow_update_branch'] as bool?, + anonymousAccessEnabled: json['anonymous_access_enabled'] as bool?, + archiveUrl: json['archive_url'] as String?, + assigneesUrl: json['assignees_url'] as String?, + blobsUrl: json['blobs_url'] as String?, + branchesUrl: json['branches_url'] as String?, + collaboratorsUrl: json['collaborators_url'] as String?, + commentsUrl: json['comments_url'] as String?, + commitsUrl: json['commits_url'] as String?, + compareUrl: json['compare_url'] as String?, + contentsUrl: json['contents_url'] as String?, + contributorsUrl: json['contributors_url'] as String?, + deleteBranchOnMerge: json['delete_branch_on_merge'] as bool?, + deploymentsUrl: json['deployments_url'] as String?, + downloadsUrl: json['downloads_url'] as String?, + eventsUrl: json['events_url'] as String?, + forks: (json['forks'] as num?)?.toInt(), + forksUrl: json['forks_url'] as String?, + gitCommitsUrl: json['git_commits_url'] as String?, + gitRefsUrl: json['git_refs_url'] as String?, + gitTagsUrl: json['git_tags_url'] as String?, + hasDiscussions: json['has_discussions'] as bool?, + hasProjects: json['has_projects'] as bool?, + hooksUrl: json['hooks_url'] as String?, + isTemplate: json['is_template'] as bool?, + issueCommentUrl: json['issue_comment_url'] as String?, + issueEventsUrl: json['issue_events_url'] as String?, + issuesUrl: json['issues_url'] as String?, + keysUrl: json['keys_url'] as String?, + labelsUrl: json['labels_url'] as String?, + languagesUrl: json['languages_url'] as String?, + masterBranch: json['master_branch'] as String?, + mergeCommitMessage: json['merge_commit_message'] as String?, + mergeCommitTitle: json['merge_commit_title'] as String?, + mergesUrl: json['merges_url'] as String?, + milestonesUrl: json['milestones_url'] as String?, + mirrorUrl: json['mirror_url'] as String?, + nodeId: json['node_id'] as String?, + notificationsUrl: json['notifications_url'] as String?, + openIssues: (json['open_issues'] as num?)?.toInt(), + organization: json['organization'] == null + ? null + : User.fromJson(json['organization'] as Map), + pullsUrl: json['pulls_url'] as String?, + releasesUrl: json['releases_url'] as String?, + squashMergeCommitMessage: json['squash_merge_commit_message'] as String?, + squashMergeCommitTitle: json['squash_merge_commit_title'] as String?, + stargazersUrl: json['stargazers_url'] as String?, + starredAt: json['starred_at'] == null + ? null + : DateTime.parse(json['starred_at'] as String), + statusesUrl: json['statuses_url'] as String?, + subscribersUrl: json['subscribers_url'] as String?, + subscriptionUrl: json['subscription_url'] as String?, + tagsUrl: json['tags_url'] as String?, + teamsUrl: json['teams_url'] as String?, + tempCloneToken: json['temp_clone_token'] as String?, + templateRepository: json['template_repository'] == null + ? null + : TemplateRepository.fromJson( + json['template_repository'] as Map), + topics: + (json['topics'] as List?)?.map((e) => e as String).toList(), + treesUrl: json['trees_url'] as String?, + url: json['url'] as String?, + visibility: json['visibility'] as String?, + watchers: (json['watchers'] as num?)?.toInt(), + webCommitSignoffRequired: json['web_commit_signoff_required'] as bool?, ); Map _$RepositoryToJson(Repository instance) => @@ -116,6 +189,71 @@ Map _$RepositoryToJson(Repository instance) => 'archived': instance.archived, 'disabled': instance.disabled, 'permissions': instance.permissions, + 'allow_auto_merge': instance.allowAutoMerge, + 'allow_forking': instance.allowForking, + 'allow_merge_commit': instance.allowMergeCommit, + 'allow_rebase_merge': instance.allowRebaseMerge, + 'allow_squash_merge': instance.allowSquashMerge, + 'allow_update_branch': instance.allowUpdateBranch, + 'anonymous_access_enabled': instance.anonymousAccessEnabled, + 'archive_url': instance.archiveUrl, + 'assignees_url': instance.assigneesUrl, + 'blobs_url': instance.blobsUrl, + 'branches_url': instance.branchesUrl, + 'collaborators_url': instance.collaboratorsUrl, + 'comments_url': instance.commentsUrl, + 'commits_url': instance.commitsUrl, + 'compare_url': instance.compareUrl, + 'contents_url': instance.contentsUrl, + 'contributors_url': instance.contributorsUrl, + 'delete_branch_on_merge': instance.deleteBranchOnMerge, + 'deployments_url': instance.deploymentsUrl, + 'downloads_url': instance.downloadsUrl, + 'events_url': instance.eventsUrl, + 'forks': instance.forks, + 'forks_url': instance.forksUrl, + 'git_commits_url': instance.gitCommitsUrl, + 'git_refs_url': instance.gitRefsUrl, + 'git_tags_url': instance.gitTagsUrl, + 'has_discussions': instance.hasDiscussions, + 'has_projects': instance.hasProjects, + 'hooks_url': instance.hooksUrl, + 'is_template': instance.isTemplate, + 'issue_comment_url': instance.issueCommentUrl, + 'issue_events_url': instance.issueEventsUrl, + 'issues_url': instance.issuesUrl, + 'keys_url': instance.keysUrl, + 'labels_url': instance.labelsUrl, + 'languages_url': instance.languagesUrl, + 'master_branch': instance.masterBranch, + 'merge_commit_message': instance.mergeCommitMessage, + 'merge_commit_title': instance.mergeCommitTitle, + 'merges_url': instance.mergesUrl, + 'milestones_url': instance.milestonesUrl, + 'mirror_url': instance.mirrorUrl, + 'node_id': instance.nodeId, + 'notifications_url': instance.notificationsUrl, + 'open_issues': instance.openIssues, + 'organization': instance.organization, + 'pulls_url': instance.pullsUrl, + 'releases_url': instance.releasesUrl, + 'squash_merge_commit_message': instance.squashMergeCommitMessage, + 'squash_merge_commit_title': instance.squashMergeCommitTitle, + 'stargazers_url': instance.stargazersUrl, + 'starred_at': instance.starredAt?.toIso8601String(), + 'statuses_url': instance.statusesUrl, + 'subscribers_url': instance.subscribersUrl, + 'subscription_url': instance.subscriptionUrl, + 'tags_url': instance.tagsUrl, + 'teams_url': instance.teamsUrl, + 'temp_clone_token': instance.tempCloneToken, + 'template_repository': instance.templateRepository, + 'topics': instance.topics, + 'trees_url': instance.treesUrl, + 'url': instance.url, + 'visibility': instance.visibility, + 'watchers': instance.watchers, + 'web_commit_signoff_required': instance.webCommitSignoffRequired, }; RepositoryPermissions _$RepositoryPermissionsFromJson( @@ -182,7 +320,7 @@ Map _$CommitDataToJson(CommitData instance) => CommitDataUser _$CommitDataUserFromJson(Map json) => CommitDataUser( json['login'] as String?, - json['id'] as int?, + (json['id'] as num?)?.toInt(), json['type'] as String?, ); @@ -209,7 +347,7 @@ Map _$CommitInfoToJson(CommitInfo instance) => UserInformation _$UserInformationFromJson(Map json) => UserInformation( json['login'] as String, - json['id'] as int, + (json['id'] as num).toInt(), json['avatar_url'] as String, json['html_url'] as String, ); @@ -242,7 +380,7 @@ CreateRepository _$CreateRepositoryFromJson(Map json) => private: json['private'] as bool?, hasIssues: json['has_issues'] as bool?, hasDownloads: json['has_downloads'] as bool?, - teamId: json['team_id'] as int?, + teamId: (json['team_id'] as num?)?.toInt(), autoInit: json['auto_init'] as bool?, gitignoreTemplate: json['gitignore_template'] as String?, licenseTemplate: json['license_template'] as String?, @@ -281,7 +419,7 @@ LicenseDetails _$LicenseDetailsFromJson(Map json) => name: json['name'] as String?, path: json['path'] as String?, sha: json['sha'] as String?, - size: json['size'] as int?, + size: (json['size'] as num?)?.toInt(), url: json['url'] == null ? null : Uri.parse(json['url'] as String), htmlUrl: json['html_url'] == null ? null diff --git a/lib/src/common/model/repos_commits.dart b/lib/src/common/model/repos_commits.dart index 940917ab..f79c774f 100644 --- a/lib/src/common/model/repos_commits.dart +++ b/lib/src/common/model/repos_commits.dart @@ -133,37 +133,56 @@ class CommitComment { this.htmlUrl, this.updatedAt, this.body, + + // Properties from the Timeline API + this.authorAssociation, + this.nodeId, + this.reactions, + this.user, }); /// Id of the comment - final int? id; + int? id; /// Relative path of the file on which the comment has been posted - final String? path; + String? path; /// Line on file - final int? line; + int? line; /// Position on the diff - final int? position; + int? position; /// SHA of the commit where the comment has been made - final String? commitId; + String? commitId; - final DateTime? createdAt; + DateTime? createdAt; /// Can be equals to [createdAt] - final DateTime? updatedAt; + DateTime? updatedAt; /// Ex: https://github.com/... - final String? htmlUrl; + String? htmlUrl; /// Ex: https://api.github.com/... @JsonKey(name: 'url') - final String? apiUrl; + String? apiUrl; /// Content of the comment - final String? body; + String? body; + + // The following properties were added to support the Timeline API. + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + String? nodeId; + + ReactionRollup? reactions; + + User? user; factory CommitComment.fromJson(Map input) => _$CommitCommentFromJson(input); diff --git a/lib/src/common/model/repos_commits.g.dart b/lib/src/common/model/repos_commits.g.dart index c91901fc..a4b3a5fe 100644 --- a/lib/src/common/model/repos_commits.g.dart +++ b/lib/src/common/model/repos_commits.g.dart @@ -47,9 +47,9 @@ Map _$RepositoryCommitToJson(RepositoryCommit instance) => }; CommitStats _$CommitStatsFromJson(Map json) => CommitStats( - additions: json['additions'] as int?, - deletions: json['deletions'] as int?, - total: json['total'] as int?, + additions: (json['additions'] as num?)?.toInt(), + deletions: (json['deletions'] as num?)?.toInt(), + total: (json['total'] as num?)?.toInt(), ); Map _$CommitStatsToJson(CommitStats instance) => @@ -61,9 +61,9 @@ Map _$CommitStatsToJson(CommitStats instance) => CommitFile _$CommitFileFromJson(Map json) => CommitFile( name: json['filename'] as String?, - additions: json['additions'] as int?, - deletions: json['deletions'] as int?, - changes: json['changes'] as int?, + additions: (json['additions'] as num?)?.toInt(), + deletions: (json['deletions'] as num?)?.toInt(), + changes: (json['changes'] as num?)?.toInt(), status: json['status'] as String?, rawUrl: json['raw_url'] as String?, blobUrl: json['blob_url'] as String?, @@ -84,9 +84,9 @@ Map _$CommitFileToJson(CommitFile instance) => CommitComment _$CommitCommentFromJson(Map json) => CommitComment( - id: json['id'] as int?, - line: json['line'] as int?, - position: json['position'] as int?, + id: (json['id'] as num?)?.toInt(), + line: (json['line'] as num?)?.toInt(), + position: (json['position'] as num?)?.toInt(), path: json['path'] as String?, apiUrl: json['url'] as String?, commitId: json['commit_id'] as String?, @@ -98,6 +98,14 @@ CommitComment _$CommitCommentFromJson(Map json) => ? null : DateTime.parse(json['updated_at'] as String), body: json['body'] as String?, + authorAssociation: json['author_association'] as String?, + nodeId: json['node_id'] as String?, + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), ); Map _$CommitCommentToJson(CommitComment instance) => @@ -112,4 +120,8 @@ Map _$CommitCommentToJson(CommitComment instance) => 'html_url': instance.htmlUrl, 'url': instance.apiUrl, 'body': instance.body, + 'author_association': instance.authorAssociation, + 'node_id': instance.nodeId, + 'reactions': instance.reactions, + 'user': instance.user, }; diff --git a/lib/src/common/model/repos_contents.g.dart b/lib/src/common/model/repos_contents.g.dart index 96f92749..84dc677b 100644 --- a/lib/src/common/model/repos_contents.g.dart +++ b/lib/src/common/model/repos_contents.g.dart @@ -9,7 +9,7 @@ part of 'repos_contents.dart'; GitHubFile _$GitHubFileFromJson(Map json) => GitHubFile( type: json['type'] as String?, encoding: json['encoding'] as String?, - size: json['size'] as int?, + size: (json['size'] as num?)?.toInt(), name: json['name'] as String?, path: json['path'] as String?, content: json['content'] as String?, diff --git a/lib/src/common/model/repos_hooks.g.dart b/lib/src/common/model/repos_hooks.g.dart index c199f2ea..fa57fa4f 100644 --- a/lib/src/common/model/repos_hooks.g.dart +++ b/lib/src/common/model/repos_hooks.g.dart @@ -7,7 +7,7 @@ part of 'repos_hooks.dart'; // ************************************************************************** Hook _$HookFromJson(Map json) => Hook( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, ) ..events = diff --git a/lib/src/common/model/repos_pages.g.dart b/lib/src/common/model/repos_pages.g.dart index a550ad75..ead9a68f 100644 --- a/lib/src/common/model/repos_pages.g.dart +++ b/lib/src/common/model/repos_pages.g.dart @@ -30,7 +30,7 @@ PageBuild _$PageBuildFromJson(Map json) => PageBuild( ? null : PageBuildPusher.fromJson(json['pusher'] as Map), commit: json['commit'] as String?, - duration: json['duration'] as int?, + duration: (json['duration'] as num?)?.toInt(), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), @@ -53,7 +53,7 @@ Map _$PageBuildToJson(PageBuild instance) => { PageBuildPusher _$PageBuildPusherFromJson(Map json) => PageBuildPusher( login: json['login'] as String?, - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), apiUrl: json['url'] as String?, htmlUrl: json['html_url'] as String?, type: json['type'] as String?, diff --git a/lib/src/common/model/repos_releases.g.dart b/lib/src/common/model/repos_releases.g.dart index 2e0998e7..e0596897 100644 --- a/lib/src/common/model/repos_releases.g.dart +++ b/lib/src/common/model/repos_releases.g.dart @@ -7,7 +7,7 @@ part of 'repos_releases.dart'; // ************************************************************************** Release _$ReleaseFromJson(Map json) => Release( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), url: json['url'] as String?, htmlUrl: json['html_url'] as String?, tarballUrl: json['tarball_url'] as String?, @@ -61,13 +61,13 @@ Map _$ReleaseToJson(Release instance) => { }; ReleaseAsset _$ReleaseAssetFromJson(Map json) => ReleaseAsset( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), name: json['name'] as String?, label: json['label'] as String?, state: json['state'] as String?, contentType: json['content_type'] as String?, - size: json['size'] as int?, - downloadCount: json['download_count'] as int?, + size: (json['size'] as num?)?.toInt(), + downloadCount: (json['download_count'] as num?)?.toInt(), browserDownloadUrl: json['browser_download_url'] as String?, createdAt: json['created_at'] == null ? null diff --git a/lib/src/common/model/repos_stats.g.dart b/lib/src/common/model/repos_stats.g.dart index 12a609a5..83bd1650 100644 --- a/lib/src/common/model/repos_stats.g.dart +++ b/lib/src/common/model/repos_stats.g.dart @@ -12,7 +12,7 @@ ContributorStatistics _$ContributorStatisticsFromJson( json['author'] == null ? null : User.fromJson(json['author'] as Map), - json['total'] as int?, + (json['total'] as num?)?.toInt(), (json['weeks'] as List?) ?.map((e) => ContributorWeekStatistics.fromJson(e as Map)) @@ -30,10 +30,10 @@ Map _$ContributorStatisticsToJson( ContributorWeekStatistics _$ContributorWeekStatisticsFromJson( Map json) => ContributorWeekStatistics( - json['w'] as int?, - json['a'] as int?, - json['d'] as int?, - json['c'] as int?, + (json['w'] as num?)?.toInt(), + (json['a'] as num?)?.toInt(), + (json['d'] as num?)?.toInt(), + (json['c'] as num?)?.toInt(), ); Map _$ContributorWeekStatisticsToJson( @@ -48,8 +48,12 @@ Map _$ContributorWeekStatisticsToJson( ContributorParticipation _$ContributorParticipationFromJson( Map json) => ContributorParticipation( - all: (json['all'] as List?)?.map((e) => e as int).toList(), - owner: (json['owner'] as List?)?.map((e) => e as int).toList(), + all: (json['all'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), + owner: (json['owner'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), ); Map _$ContributorParticipationToJson( @@ -61,9 +65,11 @@ Map _$ContributorParticipationToJson( YearCommitCountWeek _$YearCommitCountWeekFromJson(Map json) => YearCommitCountWeek( - days: (json['days'] as List?)?.map((e) => e as int).toList(), - total: json['total'] as int?, - timestamp: json['timestamp'] as int?, + days: (json['days'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), + total: (json['total'] as num?)?.toInt(), + timestamp: (json['timestamp'] as num?)?.toInt(), ); Map _$YearCommitCountWeekToJson( @@ -76,9 +82,9 @@ Map _$YearCommitCountWeekToJson( WeeklyChangesCount _$WeeklyChangesCountFromJson(Map json) => WeeklyChangesCount( - timestamp: json['timestamp'] as int?, - additions: json['additions'] as int?, - deletions: json['deletions'] as int?, + timestamp: (json['timestamp'] as num?)?.toInt(), + additions: (json['additions'] as num?)?.toInt(), + deletions: (json['deletions'] as num?)?.toInt(), ); Map _$WeeklyChangesCountToJson(WeeklyChangesCount instance) => @@ -90,9 +96,9 @@ Map _$WeeklyChangesCountToJson(WeeklyChangesCount instance) => PunchcardEntry _$PunchcardEntryFromJson(Map json) => PunchcardEntry( - weekday: json['weekday'] as int?, - hour: json['hour'] as int?, - commits: json['commits'] as int?, + weekday: (json['weekday'] as num?)?.toInt(), + hour: (json['hour'] as num?)?.toInt(), + commits: (json['commits'] as num?)?.toInt(), ); Map _$PunchcardEntryToJson(PunchcardEntry instance) => diff --git a/lib/src/common/model/repos_statuses.g.dart b/lib/src/common/model/repos_statuses.g.dart index 6cb7c947..86b7b8e3 100644 --- a/lib/src/common/model/repos_statuses.g.dart +++ b/lib/src/common/model/repos_statuses.g.dart @@ -11,7 +11,7 @@ CombinedRepositoryStatus _$CombinedRepositoryStatusFromJson( CombinedRepositoryStatus( state: json['state'] as String?, sha: json['sha'] as String?, - totalCount: json['total_count'] as int?, + totalCount: (json['total_count'] as num?)?.toInt(), statuses: (json['statuses'] as List?) ?.map((e) => RepositoryStatus.fromJson(e as Map)) .toList(), diff --git a/lib/src/common/model/search.g.dart b/lib/src/common/model/search.g.dart index d24fa9cb..9d606865 100644 --- a/lib/src/common/model/search.g.dart +++ b/lib/src/common/model/search.g.dart @@ -8,7 +8,7 @@ part of 'search.dart'; CodeSearchResults _$CodeSearchResultsFromJson(Map json) => CodeSearchResults() - ..totalCount = json['total_count'] as int? + ..totalCount = (json['total_count'] as num?)?.toInt() ..incompleteResults = json['incomplete_results'] as bool? ..items = CodeSearchItem.fromJsonList(json['items'] as List); diff --git a/lib/src/common/model/timeline.dart b/lib/src/common/model/timeline.dart new file mode 100644 index 00000000..ae2c48ef --- /dev/null +++ b/lib/src/common/model/timeline.dart @@ -0,0 +1,582 @@ +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'timeline.g.dart'; + +// Parts of this file were originally automatically generated from the response +// schema provided in the API documentation for GitHub's "List timeline events +// for an issue" API [1], using the `tool/process_github_schema.dart` script. +// +// Unfortunately, that schema contradicts the prose documentation [2] in a great +// variety of ways (for example none of the "common properties" are actually +// common to all the event types), so this code is an attempt to find the most +// pragmatic middleground between what is documented and what actually works. +// +// [1] https://docs.github.com/en/rest/issues/timeline?apiVersion=2022-11-28 +// [2] https://docs.github.com/en/webhooks-and-events/events/issue-event-types + +/// Model class for an issue or PR timeline event. +/// +/// This is a base class for the various event types. Events that only use the +/// default fields use this class; events that have additional fields use one +/// of the subclasses. +/// +/// The [TimelineEvent.fromJson] factory selects the right subclass based on +/// the [event] field. +/// +/// If the [event] type is not known, [TimelineEvent] is used. +/// +/// See also: https://docs.github.com/en/webhooks-and-events/events/issue-event-types +@JsonSerializable() +class TimelineEvent { + TimelineEvent({ + this.id = 0, + this.nodeId, + this.url, + this.actor, + this.event = '', + this.commitId, + this.commitUrl, + this.createdAt, + this.performedViaGithubApp, + }); + + int id; + String? nodeId; + String? url; + User? actor; + String event; + String? commitId; + String? commitUrl; + DateTime? createdAt; + GitHubApp? performedViaGithubApp; + + Map toJson() => _$TimelineEventToJson(this); + + factory TimelineEvent.fromJson(Map input) { + switch (input['event']) { + case 'added_to_project': + return ProjectEvent.fromJson(input); + case 'assigned': + return AssigneeEvent.fromJson(input); + case 'commented': + return CommentEvent.fromJson(input); + case 'committed': + return TimelineCommitEvent.fromJson(input); + case 'cross-referenced': + return CrossReferenceEvent.fromJson(input); + case 'demilestoned': + return MilestoneEvent.fromJson(input); + case 'labeled': + return LabelEvent.fromJson(input); + case 'locked': + return LockEvent.fromJson(input); + case 'milestoned': + return MilestoneEvent.fromJson(input); + case 'moved_columns_in_project': + return ProjectEvent.fromJson(input); + case 'removed_from_project': + return ProjectEvent.fromJson(input); + case 'renamed': + return RenameEvent.fromJson(input); + case 'review_dismissed': + return ReviewDismissedEvent.fromJson(input); + case 'review_requested': + return ReviewRequestEvent.fromJson(input); + case 'review_request_removed': + return ReviewRequestEvent.fromJson(input); + case 'reviewed': + return ReviewEvent.fromJson(input); + case 'unassigned': + return AssigneeEvent.fromJson(input); + case 'unlabeled': + return LabelEvent.fromJson(input); + default: + return _$TimelineEventFromJson(input); + } + } +} + +/// Labeled and Unlabeled Issue Events +@JsonSerializable() +class LabelEvent extends TimelineEvent { + LabelEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', // typically 'labeled' or 'unlabeled' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.label, + }); + + IssueLabel? label; + + @override + Map toJson() => _$LabelEventToJson(this); + + factory LabelEvent.fromJson(Map input) => + _$LabelEventFromJson(input); +} + +/// Milestoned and Demilestoned Issue Event +@JsonSerializable() +class MilestoneEvent extends TimelineEvent { + MilestoneEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', // typically 'milestoned' or 'demilestoned' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.milestone, + }); + + Milestone? milestone; + + @override + Map toJson() => _$MilestoneEventToJson(this); + + factory MilestoneEvent.fromJson(Map input) => + _$MilestoneEventFromJson(input); +} + +/// Renamed Issue Event +@JsonSerializable() +class RenameEvent extends TimelineEvent { + RenameEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'renamed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.rename, + }); + + Rename? rename; + + @override + Map toJson() => _$RenameEventToJson(this); + + factory RenameEvent.fromJson(Map input) => + _$RenameEventFromJson(input); +} + +/// Review Requested and Review Request Removed Issue Events +@JsonSerializable() +class ReviewRequestEvent extends TimelineEvent { + ReviewRequestEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = + '', // typically 'review_requested' or 'review_request_removed' + super.commitId, + super.commitUrl, + super.createdAt, + this.requestedReviewer, + this.requestedTeam, + this.reviewRequester, + }); + + User? requestedReviewer; + + /// Team + /// + /// Groups of organization members that gives permissions on specified repositories. + Team? requestedTeam; + + User? reviewRequester; + + @override + Map toJson() => _$ReviewRequestEventToJson(this); + + factory ReviewRequestEvent.fromJson(Map input) => + _$ReviewRequestEventFromJson(input); +} + +/// Review Dismissed Issue Event +@JsonSerializable() +class ReviewDismissedEvent extends TimelineEvent { + ReviewDismissedEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'review_dismissed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.dismissedReview, + }); + + DismissedReview? dismissedReview; + + @override + Map toJson() => _$ReviewDismissedEventToJson(this); + + factory ReviewDismissedEvent.fromJson(Map input) => + _$ReviewDismissedEventFromJson(input); +} + +/// Locked Issue Event +@JsonSerializable() +class LockEvent extends TimelineEvent { + LockEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'locked', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.lockReason, + }); + + /// Example: `"off-topic"` + String? lockReason; + + @override + Map toJson() => _$LockEventToJson(this); + + factory LockEvent.fromJson(Map input) => + _$LockEventFromJson(input); +} + +/// Added to Project, +/// Moved Columns in Project, +/// Removed from Project, and +/// Converted Note to Issue +/// Issue Events. +@JsonSerializable() +class ProjectEvent extends TimelineEvent { + ProjectEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event, // typically one of 'added_to_project', 'moved_columns_in_project', 'removed_from_project', 'converted_note_to_issue' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.projectCard, + }); + + ProjectCard? projectCard; + + @override + Map toJson() => _$ProjectEventToJson(this); + + factory ProjectEvent.fromJson(Map input) => + _$ProjectEventFromJson(input); +} + +/// Timeline Comment Event +@JsonSerializable() +class CommentEvent extends TimelineEvent { + CommentEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'commented', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.authorAssociation, + this.body, + this.bodyHtml, + this.bodyText, + this.htmlUrl, + this.issueUrl, + this.reactions, + this.updatedAt, + this.user, + }); + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + /// Contents of the issue comment + /// + /// Example: `What version of Safari were you using when you observed this bug?` + String? body; + + String? bodyHtml; + String? bodyText; + + String? htmlUrl; + + String? issueUrl; + + ReactionRollup? reactions; + + DateTime? updatedAt; + + User? user; + + @override + Map toJson() => _$CommentEventToJson(this); + + factory CommentEvent.fromJson(Map input) => + _$CommentEventFromJson(input); +} + +/// Timeline Cross Referenced Event +@JsonSerializable() +class CrossReferenceEvent extends TimelineEvent { + CrossReferenceEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'cross-referenced', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.source, + this.updatedAt, + }); + + Source? source; + + DateTime? updatedAt; + + @override + Map toJson() => _$CrossReferenceEventToJson(this); + + factory CrossReferenceEvent.fromJson(Map input) => + _$CrossReferenceEventFromJson(input); +} + +/// Timeline Committed Event +@JsonSerializable() +class TimelineCommitEvent extends TimelineEvent { + TimelineCommitEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'committed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.author, + this.committer, + this.htmlUrl, + this.message, + this.parents, + this.sha, + this.tree, + this.verification, + }); + + User? author; + + /// Identifying information for the git-user + User? committer; + + /// Format: uri + String? htmlUrl; + + /// Message describing the purpose of the commit + String? message; + + List? parents; + + /// SHA for the commit + /// + /// Example: `7638417db6d59f3c431d3e1f261cc637155684cd` + String? sha; + + Tree? tree; + + Verification? verification; + + @override + Map toJson() => _$TimelineCommitEventToJson(this); + + factory TimelineCommitEvent.fromJson(Map input) => + _$TimelineCommitEventFromJson(input); +} + +/// Timeline Reviewed Event +@JsonSerializable() +class ReviewEvent extends TimelineEvent { + ReviewEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'reviewed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.authorAssociation, + this.body, + this.bodyHtml, + this.bodyText, + this.htmlUrl, + this.links, + this.pullRequestUrl, + this.state, + this.submittedAt, + this.user, + }); + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + /// The text of the review. + /// + /// Example: `This looks great.` + String? body; + + String? bodyHtml; + String? bodyText; + + /// Example: `https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80` + String? htmlUrl; + + @JsonKey(name: '_links') + ReviewLinks? links; + + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/12` + String? pullRequestUrl; + + /// Example: `CHANGES_REQUESTED` + String? state; + + DateTime? submittedAt; + + User? user; + + @override + Map toJson() => _$ReviewEventToJson(this); + + factory ReviewEvent.fromJson(Map input) => + _$ReviewEventFromJson(input); +} + +/// Timeline Line Commented Event +@JsonSerializable() +class TimelineLineCommentedEvent extends TimelineEvent { + TimelineLineCommentedEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.comments, + }); + + List? comments; + + @override + Map toJson() => _$TimelineLineCommentedEventToJson(this); + + factory TimelineLineCommentedEvent.fromJson(Map input) => + _$TimelineLineCommentedEventFromJson(input); +} + +/// Timeline Commit Commented Event +@JsonSerializable() +class TimelineCommitCommentedEvent extends TimelineEvent { + TimelineCommitCommentedEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.comments, + }); + + List? comments; + + @override + Map toJson() => _$TimelineCommitCommentedEventToJson(this); + + factory TimelineCommitCommentedEvent.fromJson(Map input) => + _$TimelineCommitCommentedEventFromJson(input); +} + +/// Timeline Assigned and Timeline Unassigned Issue Events +@JsonSerializable() +class AssigneeEvent extends TimelineEvent { + AssigneeEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event, // typically 'assigned' or 'unassigned' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.assignee, + }); + + User? assignee; + + @override + Map toJson() => _$AssigneeEventToJson(this); + + factory AssigneeEvent.fromJson(Map input) => + _$AssigneeEventFromJson(input); +} + +/// State Change Issue Event +@JsonSerializable() +class StateChangeIssueEvent extends TimelineEvent { + StateChangeIssueEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.stateReason, + }); + + String? stateReason; + + @override + Map toJson() => _$StateChangeIssueEventToJson(this); + + factory StateChangeIssueEvent.fromJson(Map input) => + _$StateChangeIssueEventFromJson(input); +} diff --git a/lib/src/common/model/timeline.g.dart b/lib/src/common/model/timeline.g.dart new file mode 100644 index 00000000..be7d916d --- /dev/null +++ b/lib/src/common/model/timeline.g.dart @@ -0,0 +1,671 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'timeline.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TimelineEvent _$TimelineEventFromJson(Map json) => + TimelineEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + ); + +Map _$TimelineEventToJson(TimelineEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + }; + +LabelEvent _$LabelEventFromJson(Map json) => LabelEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + label: json['label'] == null + ? null + : IssueLabel.fromJson(json['label'] as Map), + ); + +Map _$LabelEventToJson(LabelEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'label': instance.label, + }; + +MilestoneEvent _$MilestoneEventFromJson(Map json) => + MilestoneEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + milestone: json['milestone'] == null + ? null + : Milestone.fromJson(json['milestone'] as Map), + ); + +Map _$MilestoneEventToJson(MilestoneEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'milestone': instance.milestone, + }; + +RenameEvent _$RenameEventFromJson(Map json) => RenameEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'renamed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + rename: json['rename'] == null + ? null + : Rename.fromJson(json['rename'] as Map), + ); + +Map _$RenameEventToJson(RenameEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'rename': instance.rename, + }; + +ReviewRequestEvent _$ReviewRequestEventFromJson(Map json) => + ReviewRequestEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + requestedReviewer: json['requested_reviewer'] == null + ? null + : User.fromJson(json['requested_reviewer'] as Map), + requestedTeam: json['requested_team'] == null + ? null + : Team.fromJson(json['requested_team'] as Map), + reviewRequester: json['review_requester'] == null + ? null + : User.fromJson(json['review_requester'] as Map), + )..performedViaGithubApp = json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map); + +Map _$ReviewRequestEventToJson(ReviewRequestEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'requested_reviewer': instance.requestedReviewer, + 'requested_team': instance.requestedTeam, + 'review_requester': instance.reviewRequester, + }; + +ReviewDismissedEvent _$ReviewDismissedEventFromJson( + Map json) => + ReviewDismissedEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'review_dismissed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + dismissedReview: json['dismissed_review'] == null + ? null + : DismissedReview.fromJson( + json['dismissed_review'] as Map), + ); + +Map _$ReviewDismissedEventToJson( + ReviewDismissedEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'dismissed_review': instance.dismissedReview, + }; + +LockEvent _$LockEventFromJson(Map json) => LockEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'locked', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + lockReason: json['lock_reason'] as String?, + ); + +Map _$LockEventToJson(LockEvent instance) => { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'lock_reason': instance.lockReason, + }; + +ProjectEvent _$ProjectEventFromJson(Map json) => ProjectEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + projectCard: json['project_card'] == null + ? null + : ProjectCard.fromJson(json['project_card'] as Map), + ); + +Map _$ProjectEventToJson(ProjectEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'project_card': instance.projectCard, + }; + +CommentEvent _$CommentEventFromJson(Map json) => CommentEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'commented', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + authorAssociation: json['author_association'] as String?, + body: json['body'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + htmlUrl: json['html_url'] as String?, + issueUrl: json['issue_url'] as String?, + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$CommentEventToJson(CommentEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'author_association': instance.authorAssociation, + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'html_url': instance.htmlUrl, + 'issue_url': instance.issueUrl, + 'reactions': instance.reactions, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'user': instance.user, + }; + +CrossReferenceEvent _$CrossReferenceEventFromJson(Map json) => + CrossReferenceEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'cross-referenced', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + source: json['source'] == null + ? null + : Source.fromJson(json['source'] as Map), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$CrossReferenceEventToJson( + CrossReferenceEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'source': instance.source, + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +TimelineCommitEvent _$TimelineCommitEventFromJson(Map json) => + TimelineCommitEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'committed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + author: json['author'] == null + ? null + : User.fromJson(json['author'] as Map), + committer: json['committer'] == null + ? null + : User.fromJson(json['committer'] as Map), + htmlUrl: json['html_url'] as String?, + message: json['message'] as String?, + parents: (json['parents'] as List?) + ?.map((e) => Tree.fromJson(e as Map)) + .toList(), + sha: json['sha'] as String?, + tree: json['tree'] == null + ? null + : Tree.fromJson(json['tree'] as Map), + verification: json['verification'] == null + ? null + : Verification.fromJson(json['verification'] as Map), + ); + +Map _$TimelineCommitEventToJson( + TimelineCommitEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'author': instance.author, + 'committer': instance.committer, + 'html_url': instance.htmlUrl, + 'message': instance.message, + 'parents': instance.parents, + 'sha': instance.sha, + 'tree': instance.tree, + 'verification': instance.verification, + }; + +ReviewEvent _$ReviewEventFromJson(Map json) => ReviewEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'reviewed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + authorAssociation: json['author_association'] as String?, + body: json['body'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + htmlUrl: json['html_url'] as String?, + links: json['_links'] == null + ? null + : ReviewLinks.fromJson(json['_links'] as Map), + pullRequestUrl: json['pull_request_url'] as String?, + state: json['state'] as String?, + submittedAt: json['submitted_at'] == null + ? null + : DateTime.parse(json['submitted_at'] as String), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$ReviewEventToJson(ReviewEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'author_association': instance.authorAssociation, + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'html_url': instance.htmlUrl, + '_links': instance.links, + 'pull_request_url': instance.pullRequestUrl, + 'state': instance.state, + 'submitted_at': instance.submittedAt?.toIso8601String(), + 'user': instance.user, + }; + +TimelineLineCommentedEvent _$TimelineLineCommentedEventFromJson( + Map json) => + TimelineLineCommentedEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + comments: (json['comments'] as List?) + ?.map((e) => + PullRequestReviewComment.fromJson(e as Map)) + .toList(), + ); + +Map _$TimelineLineCommentedEventToJson( + TimelineLineCommentedEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'comments': instance.comments, + }; + +TimelineCommitCommentedEvent _$TimelineCommitCommentedEventFromJson( + Map json) => + TimelineCommitCommentedEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + comments: (json['comments'] as List?) + ?.map((e) => CommitComment.fromJson(e as Map)) + .toList(), + ); + +Map _$TimelineCommitCommentedEventToJson( + TimelineCommitCommentedEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'comments': instance.comments, + }; + +AssigneeEvent _$AssigneeEventFromJson(Map json) => + AssigneeEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + assignee: json['assignee'] == null + ? null + : User.fromJson(json['assignee'] as Map), + ); + +Map _$AssigneeEventToJson(AssigneeEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'assignee': instance.assignee, + }; + +StateChangeIssueEvent _$StateChangeIssueEventFromJson( + Map json) => + StateChangeIssueEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + stateReason: json['state_reason'] as String?, + ); + +Map _$StateChangeIssueEventToJson( + StateChangeIssueEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'state_reason': instance.stateReason, + }; diff --git a/lib/src/common/model/timeline_support.dart b/lib/src/common/model/timeline_support.dart new file mode 100644 index 00000000..f2b03c62 --- /dev/null +++ b/lib/src/common/model/timeline_support.dart @@ -0,0 +1,562 @@ +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'timeline_support.g.dart'; + +/// GitHub app +/// +/// GitHub apps are a new way to extend GitHub. They can be installed directly +/// on organizations and user accounts and granted access to specific repositories. +/// They come with granular permissions and built-in webhooks. GitHub apps are +/// first class actors within GitHub. +@JsonSerializable() +class GitHubApp { + GitHubApp({ + this.clientId, + this.clientSecret, + this.createdAt, + this.description, + this.events, + this.externalUrl, + this.htmlUrl, + this.id, + this.installationsCount, + this.name, + this.nodeId, + this.owner, + this.pem, + this.permissions, + this.slug, + this.updatedAt, + this.webhookSecret, + }); + + /// Example: `"Iv1.25b5d1e65ffc4022"` + final String? clientId; + + /// Example: `"1d4b2097ac622ba702d19de498f005747a8b21d3"` + final String? clientSecret; + + final DateTime? createdAt; + + final String? description; + + /// The list of events for the GitHub app + /// + /// Example: `label` + /// + /// Example: `deployment` + final List? events; + + /// Example: `https://example.com` + final String? externalUrl; + + /// Example: `https://github.com/apps/super-ci` + final String? htmlUrl; + + /// Unique identifier of the GitHub app + final int? id; + + /// The number of installations associated with the GitHub app + final int? installationsCount; + + /// The name of the GitHub app + /// + /// Example: `Probot Owners` + final String? name; + + /// Example: `MDExOkludGVncmF0aW9uMQ==` + final String? nodeId; + + final User? owner; + + /// Example: + /// + /// ``` + /// -----BEGIN RSA PRIVATE KEY----- + /// MIIEogIBAAKCAQEArYxrNYD/iT5CZVpRJu4rBKmmze3PVmT/gCo2ATUvDvZTPTey + /// xcGJ3vvrJXazKk06pN05TN29o98jrYz4cengG3YGsXPNEpKsIrEl8NhbnxapEnM9 + /// JCMRe0P5JcPsfZlX6hmiT7136GRWiGOUba2X9+HKh8QJVLG5rM007TBER9/z9mWm + /// rJuNh+m5l320oBQY/Qq3A7wzdEfZw8qm/mIN0FCeoXH1L6B8xXWaAYBwhTEh6SSn + /// ZHlO1Xu1JWDmAvBCi0RO5aRSKM8q9QEkvvHP4yweAtK3N8+aAbZ7ovaDhyGz8r6r + /// zhU1b8Uo0Z2ysf503WqzQgIajr7Fry7/kUwpgQIDAQABAoIBADwJp80Ko1xHPZDy + /// fcCKBDfIuPvkmSW6KumbsLMaQv1aGdHDwwTGv3t0ixSay8CGlxMRtRDyZPib6SvQ + /// 6OH/lpfpbMdW2ErkksgtoIKBVrDilfrcAvrNZu7NxRNbhCSvN8q0s4ICecjbbVQh + /// nueSdlA6vGXbW58BHMq68uRbHkP+k+mM9U0mDJ1HMch67wlg5GbayVRt63H7R2+r + /// Vxcna7B80J/lCEjIYZznawgiTvp3MSanTglqAYi+m1EcSsP14bJIB9vgaxS79kTu + /// oiSo93leJbBvuGo8QEiUqTwMw4tDksmkLsoqNKQ1q9P7LZ9DGcujtPy4EZsamSJT + /// y8OJt0ECgYEA2lxOxJsQk2kI325JgKFjo92mQeUObIvPfSNWUIZQDTjniOI6Gv63 + /// GLWVFrZcvQBWjMEQraJA9xjPbblV8PtfO87MiJGLWCHFxmPz2dzoedN+2Coxom8m + /// V95CLz8QUShuao6u/RYcvUaZEoYs5bHcTmy5sBK80JyEmafJPtCQVxMCgYEAy3ar + /// Zr3yv4xRPEPMat4rseswmuMooSaK3SKub19WFI5IAtB/e7qR1Rj9JhOGcZz+OQrl + /// T78O2OFYlgOIkJPvRMrPpK5V9lslc7tz1FSh3BZMRGq5jSyD7ETSOQ0c8T2O/s7v + /// beEPbVbDe4mwvM24XByH0GnWveVxaDl51ABD65sCgYB3ZAspUkOA5egVCh8kNpnd + /// Sd6SnuQBE3ySRlT2WEnCwP9Ph6oPgn+oAfiPX4xbRqkL8q/k0BdHQ4h+zNwhk7+h + /// WtPYRAP1Xxnc/F+jGjb+DVaIaKGU18MWPg7f+FI6nampl3Q0KvfxwX0GdNhtio8T + /// Tj1E+SnFwh56SRQuxSh2gwKBgHKjlIO5NtNSflsUYFM+hyQiPiqnHzddfhSG+/3o + /// m5nNaSmczJesUYreH5San7/YEy2UxAugvP7aSY2MxB+iGsiJ9WD2kZzTUlDZJ7RV + /// UzWsoqBR+eZfVJ2FUWWvy8TpSG6trh4dFxImNtKejCR1TREpSiTV3Zb1dmahK9GV + /// rK9NAoGAbBxRLoC01xfxCTgt5BDiBcFVh4fp5yYKwavJPLzHSpuDOrrI9jDn1oKN + /// onq5sDU1i391zfQvdrbX4Ova48BN+B7p63FocP/MK5tyyBoT8zQEk2+vWDOw7H/Z + /// u5dTCPxTIsoIwUw1I+7yIxqJzLPFgR2gVBwY1ra/8iAqCj+zeBw= + /// -----END RSA PRIVATE KEY----- + /// ``` + final String? pem; + + /// The set of permissions for the GitHub app + final Permissions? permissions; + + /// The slug name of the GitHub app + /// + /// Example: `probot-owners` + final String? slug; + + final DateTime? updatedAt; + + /// Example: `"6fba8f2fc8a7e8f2cca5577eddd82ca7586b3b6b"` + final String? webhookSecret; + + Map toJson() => _$GitHubAppToJson(this); + + factory GitHubApp.fromJson(Map input) => + _$GitHubAppFromJson(input); +} + +@JsonSerializable() +class Rename { + Rename({ + this.from, + this.to, + }); + + final String? from; + final String? to; + + Map toJson() => _$RenameToJson(this); + + factory Rename.fromJson(Map input) => + _$RenameFromJson(input); +} + +@JsonSerializable() +class DismissedReview { + DismissedReview({ + this.dismissalCommitId, + this.dismissalMessage, + this.reviewId, + this.state, + }); + + final String? dismissalCommitId; + final String? dismissalMessage; + final int? reviewId; + final String? state; + + Map toJson() => _$DismissedReviewToJson(this); + + factory DismissedReview.fromJson(Map input) => + _$DismissedReviewFromJson(input); +} + +@JsonSerializable() +class ProjectCard { + ProjectCard({ + this.columnName, + this.id, + this.previousColumnName, + this.projectId, + this.projectUrl, + this.url, + }); + + final String? columnName; + final int? id; + final String? previousColumnName; + final int? projectId; + final String? projectUrl; + final String? url; + + Map toJson() => _$ProjectCardToJson(this); + + factory ProjectCard.fromJson(Map input) => + _$ProjectCardFromJson(input); +} + +@JsonSerializable() +class Source { + Source({ + this.issue, + this.type, + }); + + final Issue? issue; + final String? type; + + Map toJson() => _$SourceToJson(this); + + factory Source.fromJson(Map input) => + _$SourceFromJson(input); +} + +/// License +@JsonSerializable() +class License { + License({ + this.htmlUrl, + this.key, + this.name, + this.nodeId, + this.spdxId, + this.url, + }); + + final String? htmlUrl; + + /// Example: `mit` + final String? key; + + /// Example: `MIT License` + final String? name; + + /// Example: `MDc6TGljZW5zZW1pdA==` + final String? nodeId; + + /// Example: `MIT` + final String? spdxId; + + /// Example: `https://api.github.com/licenses/mit` + final String? url; + + Map toJson() => _$LicenseToJson(this); + + factory License.fromJson(Map input) => + _$LicenseFromJson(input); +} + +@JsonSerializable() +class TemplateRepository { + TemplateRepository({ + this.allowAutoMerge, + this.allowMergeCommit, + this.allowRebaseMerge, + this.allowSquashMerge, + this.allowUpdateBranch, + this.archiveUrl, + this.archived, + this.assigneesUrl, + this.blobsUrl, + this.branchesUrl, + this.cloneUrl, + this.collaboratorsUrl, + this.commentsUrl, + this.commitsUrl, + this.compareUrl, + this.contentsUrl, + this.contributorsUrl, + this.createdAt, + this.defaultBranch, + this.deleteBranchOnMerge, + this.deploymentsUrl, + this.description, + this.disabled, + this.downloadsUrl, + this.eventsUrl, + this.fork, + this.forksCount, + this.forksUrl, + this.fullName, + this.gitCommitsUrl, + this.gitRefsUrl, + this.gitTagsUrl, + this.gitUrl, + this.hasDownloads, + this.hasIssues, + this.hasPages, + this.hasProjects, + this.hasWiki, + this.homepage, + this.hooksUrl, + this.htmlUrl, + this.id, + this.isTemplate, + this.issueCommentUrl, + this.issueEventsUrl, + this.issuesUrl, + this.keysUrl, + this.labelsUrl, + this.language, + this.languagesUrl, + this.mergeCommitMessage, + this.mergeCommitTitle, + this.mergesUrl, + this.milestonesUrl, + this.mirrorUrl, + this.name, + this.networkCount, + this.nodeId, + this.notificationsUrl, + this.openIssuesCount, + this.owner, + this.permissions, + this.private, + this.pullsUrl, + this.pushedAt, + this.releasesUrl, + this.size, + this.squashMergeCommitMessage, + this.squashMergeCommitTitle, + this.sshUrl, + this.stargazersCount, + this.stargazersUrl, + this.statusesUrl, + this.subscribersCount, + this.subscribersUrl, + this.subscriptionUrl, + this.svnUrl, + this.tagsUrl, + this.teamsUrl, + this.tempCloneToken, + this.topics, + this.treesUrl, + this.updatedAt, + this.url, + this.visibility, + this.watchersCount, + }); + + final bool? allowAutoMerge; + final bool? allowMergeCommit; + final bool? allowRebaseMerge; + final bool? allowSquashMerge; + final bool? allowUpdateBranch; + final String? archiveUrl; + final bool? archived; + final String? assigneesUrl; + final String? blobsUrl; + final String? branchesUrl; + final String? cloneUrl; + final String? collaboratorsUrl; + final String? commentsUrl; + final String? commitsUrl; + final String? compareUrl; + final String? contentsUrl; + final String? contributorsUrl; + final DateTime? createdAt; + final String? defaultBranch; + final bool? deleteBranchOnMerge; + final String? deploymentsUrl; + final String? description; + final bool? disabled; + final String? downloadsUrl; + final String? eventsUrl; + final bool? fork; + final int? forksCount; + final String? forksUrl; + final String? fullName; + final String? gitCommitsUrl; + final String? gitRefsUrl; + final String? gitTagsUrl; + final String? gitUrl; + final bool? hasDownloads; + final bool? hasIssues; + final bool? hasPages; + final bool? hasProjects; + final bool? hasWiki; + final String? homepage; + final String? hooksUrl; + final String? htmlUrl; + final int? id; + final bool? isTemplate; + final String? issueCommentUrl; + final String? issueEventsUrl; + final String? issuesUrl; + final String? keysUrl; + final String? labelsUrl; + final String? language; + final String? languagesUrl; + + /// The default value for a merge commit message. + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `PR_BODY` - default to the pull request's body. + /// - `BLANK` - default to a blank commit message. + final String? mergeCommitMessage; + + /// The default value for a merge commit title. + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., + /// Merge pull request #123 from branch-name). + final String? mergeCommitTitle; + + final String? mergesUrl; + final String? milestonesUrl; + final String? mirrorUrl; + final String? name; + final int? networkCount; + final String? nodeId; + final String? notificationsUrl; + final int? openIssuesCount; + final Owner? owner; + final Permissions? permissions; + final bool? private; + final String? pullsUrl; + final DateTime? pushedAt; + final String? releasesUrl; + final int? size; + + /// The default value for a squash merge commit message: + /// + /// - `PR_BODY` - default to the pull request's body. + /// - `COMMIT_MESSAGES` - default to the branch's commit messages. + /// - `BLANK` - default to a blank commit message. + final String? squashMergeCommitMessage; + + /// The default value for a squash merge commit title: + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) + /// or the pull request's title (when more than one commit). + final String? squashMergeCommitTitle; + + final String? sshUrl; + final int? stargazersCount; + final String? stargazersUrl; + final String? statusesUrl; + final int? subscribersCount; + final String? subscribersUrl; + final String? subscriptionUrl; + final String? svnUrl; + final String? tagsUrl; + final String? teamsUrl; + final String? tempCloneToken; + final List? topics; + final String? treesUrl; + final DateTime? updatedAt; + final String? url; + final String? visibility; + final int? watchersCount; + + Map toJson() => _$TemplateRepositoryToJson(this); + + factory TemplateRepository.fromJson(Map input) => + _$TemplateRepositoryFromJson(input); +} + +@JsonSerializable() +class Owner { + Owner({ + this.avatarUrl, + this.eventsUrl, + this.followersUrl, + this.followingUrl, + this.gistsUrl, + this.gravatarId, + this.htmlUrl, + this.id, + this.login, + this.nodeId, + this.organizationsUrl, + this.receivedEventsUrl, + this.reposUrl, + this.siteAdmin, + this.starredUrl, + this.subscriptionsUrl, + this.type, + this.url, + }); + + final String? avatarUrl; + final String? eventsUrl; + final String? followersUrl; + final String? followingUrl; + final String? gistsUrl; + final String? gravatarId; + final String? htmlUrl; + final int? id; + final String? login; + final String? nodeId; + final String? organizationsUrl; + final String? receivedEventsUrl; + final String? reposUrl; + final bool? siteAdmin; + final String? starredUrl; + final String? subscriptionsUrl; + final String? type; + final String? url; + + Map toJson() => _$OwnerToJson(this); + + factory Owner.fromJson(Map input) => _$OwnerFromJson(input); +} + +@JsonSerializable() +class Tree { + Tree({ + this.sha, + this.url, + this.htmlUrl, + }); + + /// SHA for the commit + /// + /// Example: `7638417db6d59f3c431d3e1f261cc637155684cd` + final String? sha; + + final String? url; + + final String? htmlUrl; + + Map toJson() => _$TreeToJson(this); + + factory Tree.fromJson(Map input) => _$TreeFromJson(input); +} + +@JsonSerializable() +class Verification { + Verification({ + this.payload, + this.reason, + this.signature, + this.verified, + }); + + final String? payload; + final String? reason; + final String? signature; + final bool? verified; + + Map toJson() => _$VerificationToJson(this); + + factory Verification.fromJson(Map input) => + _$VerificationFromJson(input); +} + +@JsonSerializable() +class HtmlLink { + HtmlLink({ + this.href, + }); + + final String? href; + + Map toJson() => _$HtmlLinkToJson(this); + + factory HtmlLink.fromJson(Map input) => + _$HtmlLinkFromJson(input); +} + +@JsonSerializable() +class PullRequestLink { + PullRequestLink({ + this.href, + }); + + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/1` + final String? href; + + Map toJson() => _$PullRequestLinkToJson(this); + + factory PullRequestLink.fromJson(Map input) => + _$PullRequestLinkFromJson(input); +} diff --git a/lib/src/common/model/timeline_support.g.dart b/lib/src/common/model/timeline_support.g.dart new file mode 100644 index 00000000..83adf60a --- /dev/null +++ b/lib/src/common/model/timeline_support.g.dart @@ -0,0 +1,409 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'timeline_support.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GitHubApp _$GitHubAppFromJson(Map json) => GitHubApp( + clientId: json['client_id'] as String?, + clientSecret: json['client_secret'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + events: + (json['events'] as List?)?.map((e) => e as String).toList(), + externalUrl: json['external_url'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + installationsCount: (json['installations_count'] as num?)?.toInt(), + name: json['name'] as String?, + nodeId: json['node_id'] as String?, + owner: json['owner'] == null + ? null + : User.fromJson(json['owner'] as Map), + pem: json['pem'] as String?, + permissions: json['permissions'] == null + ? null + : Permissions.fromJson(json['permissions'] as Map), + slug: json['slug'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + webhookSecret: json['webhook_secret'] as String?, + ); + +Map _$GitHubAppToJson(GitHubApp instance) => { + 'client_id': instance.clientId, + 'client_secret': instance.clientSecret, + 'created_at': instance.createdAt?.toIso8601String(), + 'description': instance.description, + 'events': instance.events, + 'external_url': instance.externalUrl, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'installations_count': instance.installationsCount, + 'name': instance.name, + 'node_id': instance.nodeId, + 'owner': instance.owner, + 'pem': instance.pem, + 'permissions': instance.permissions, + 'slug': instance.slug, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'webhook_secret': instance.webhookSecret, + }; + +Rename _$RenameFromJson(Map json) => Rename( + from: json['from'] as String?, + to: json['to'] as String?, + ); + +Map _$RenameToJson(Rename instance) => { + 'from': instance.from, + 'to': instance.to, + }; + +DismissedReview _$DismissedReviewFromJson(Map json) => + DismissedReview( + dismissalCommitId: json['dismissal_commit_id'] as String?, + dismissalMessage: json['dismissal_message'] as String?, + reviewId: (json['review_id'] as num?)?.toInt(), + state: json['state'] as String?, + ); + +Map _$DismissedReviewToJson(DismissedReview instance) => + { + 'dismissal_commit_id': instance.dismissalCommitId, + 'dismissal_message': instance.dismissalMessage, + 'review_id': instance.reviewId, + 'state': instance.state, + }; + +ProjectCard _$ProjectCardFromJson(Map json) => ProjectCard( + columnName: json['column_name'] as String?, + id: (json['id'] as num?)?.toInt(), + previousColumnName: json['previous_column_name'] as String?, + projectId: (json['project_id'] as num?)?.toInt(), + projectUrl: json['project_url'] as String?, + url: json['url'] as String?, + ); + +Map _$ProjectCardToJson(ProjectCard instance) => + { + 'column_name': instance.columnName, + 'id': instance.id, + 'previous_column_name': instance.previousColumnName, + 'project_id': instance.projectId, + 'project_url': instance.projectUrl, + 'url': instance.url, + }; + +Source _$SourceFromJson(Map json) => Source( + issue: json['issue'] == null + ? null + : Issue.fromJson(json['issue'] as Map), + type: json['type'] as String?, + ); + +Map _$SourceToJson(Source instance) => { + 'issue': instance.issue, + 'type': instance.type, + }; + +License _$LicenseFromJson(Map json) => License( + htmlUrl: json['html_url'] as String?, + key: json['key'] as String?, + name: json['name'] as String?, + nodeId: json['node_id'] as String?, + spdxId: json['spdx_id'] as String?, + url: json['url'] as String?, + ); + +Map _$LicenseToJson(License instance) => { + 'html_url': instance.htmlUrl, + 'key': instance.key, + 'name': instance.name, + 'node_id': instance.nodeId, + 'spdx_id': instance.spdxId, + 'url': instance.url, + }; + +TemplateRepository _$TemplateRepositoryFromJson(Map json) => + TemplateRepository( + allowAutoMerge: json['allow_auto_merge'] as bool?, + allowMergeCommit: json['allow_merge_commit'] as bool?, + allowRebaseMerge: json['allow_rebase_merge'] as bool?, + allowSquashMerge: json['allow_squash_merge'] as bool?, + allowUpdateBranch: json['allow_update_branch'] as bool?, + archiveUrl: json['archive_url'] as String?, + archived: json['archived'] as bool?, + assigneesUrl: json['assignees_url'] as String?, + blobsUrl: json['blobs_url'] as String?, + branchesUrl: json['branches_url'] as String?, + cloneUrl: json['clone_url'] as String?, + collaboratorsUrl: json['collaborators_url'] as String?, + commentsUrl: json['comments_url'] as String?, + commitsUrl: json['commits_url'] as String?, + compareUrl: json['compare_url'] as String?, + contentsUrl: json['contents_url'] as String?, + contributorsUrl: json['contributors_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + defaultBranch: json['default_branch'] as String?, + deleteBranchOnMerge: json['delete_branch_on_merge'] as bool?, + deploymentsUrl: json['deployments_url'] as String?, + description: json['description'] as String?, + disabled: json['disabled'] as bool?, + downloadsUrl: json['downloads_url'] as String?, + eventsUrl: json['events_url'] as String?, + fork: json['fork'] as bool?, + forksCount: (json['forks_count'] as num?)?.toInt(), + forksUrl: json['forks_url'] as String?, + fullName: json['full_name'] as String?, + gitCommitsUrl: json['git_commits_url'] as String?, + gitRefsUrl: json['git_refs_url'] as String?, + gitTagsUrl: json['git_tags_url'] as String?, + gitUrl: json['git_url'] as String?, + hasDownloads: json['has_downloads'] as bool?, + hasIssues: json['has_issues'] as bool?, + hasPages: json['has_pages'] as bool?, + hasProjects: json['has_projects'] as bool?, + hasWiki: json['has_wiki'] as bool?, + homepage: json['homepage'] as String?, + hooksUrl: json['hooks_url'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + isTemplate: json['is_template'] as bool?, + issueCommentUrl: json['issue_comment_url'] as String?, + issueEventsUrl: json['issue_events_url'] as String?, + issuesUrl: json['issues_url'] as String?, + keysUrl: json['keys_url'] as String?, + labelsUrl: json['labels_url'] as String?, + language: json['language'] as String?, + languagesUrl: json['languages_url'] as String?, + mergeCommitMessage: json['merge_commit_message'] as String?, + mergeCommitTitle: json['merge_commit_title'] as String?, + mergesUrl: json['merges_url'] as String?, + milestonesUrl: json['milestones_url'] as String?, + mirrorUrl: json['mirror_url'] as String?, + name: json['name'] as String?, + networkCount: (json['network_count'] as num?)?.toInt(), + nodeId: json['node_id'] as String?, + notificationsUrl: json['notifications_url'] as String?, + openIssuesCount: (json['open_issues_count'] as num?)?.toInt(), + owner: json['owner'] == null + ? null + : Owner.fromJson(json['owner'] as Map), + permissions: json['permissions'] == null + ? null + : Permissions.fromJson(json['permissions'] as Map), + private: json['private'] as bool?, + pullsUrl: json['pulls_url'] as String?, + pushedAt: json['pushed_at'] == null + ? null + : DateTime.parse(json['pushed_at'] as String), + releasesUrl: json['releases_url'] as String?, + size: (json['size'] as num?)?.toInt(), + squashMergeCommitMessage: json['squash_merge_commit_message'] as String?, + squashMergeCommitTitle: json['squash_merge_commit_title'] as String?, + sshUrl: json['ssh_url'] as String?, + stargazersCount: (json['stargazers_count'] as num?)?.toInt(), + stargazersUrl: json['stargazers_url'] as String?, + statusesUrl: json['statuses_url'] as String?, + subscribersCount: (json['subscribers_count'] as num?)?.toInt(), + subscribersUrl: json['subscribers_url'] as String?, + subscriptionUrl: json['subscription_url'] as String?, + svnUrl: json['svn_url'] as String?, + tagsUrl: json['tags_url'] as String?, + teamsUrl: json['teams_url'] as String?, + tempCloneToken: json['temp_clone_token'] as String?, + topics: + (json['topics'] as List?)?.map((e) => e as String).toList(), + treesUrl: json['trees_url'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + url: json['url'] as String?, + visibility: json['visibility'] as String?, + watchersCount: (json['watchers_count'] as num?)?.toInt(), + ); + +Map _$TemplateRepositoryToJson(TemplateRepository instance) => + { + 'allow_auto_merge': instance.allowAutoMerge, + 'allow_merge_commit': instance.allowMergeCommit, + 'allow_rebase_merge': instance.allowRebaseMerge, + 'allow_squash_merge': instance.allowSquashMerge, + 'allow_update_branch': instance.allowUpdateBranch, + 'archive_url': instance.archiveUrl, + 'archived': instance.archived, + 'assignees_url': instance.assigneesUrl, + 'blobs_url': instance.blobsUrl, + 'branches_url': instance.branchesUrl, + 'clone_url': instance.cloneUrl, + 'collaborators_url': instance.collaboratorsUrl, + 'comments_url': instance.commentsUrl, + 'commits_url': instance.commitsUrl, + 'compare_url': instance.compareUrl, + 'contents_url': instance.contentsUrl, + 'contributors_url': instance.contributorsUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'default_branch': instance.defaultBranch, + 'delete_branch_on_merge': instance.deleteBranchOnMerge, + 'deployments_url': instance.deploymentsUrl, + 'description': instance.description, + 'disabled': instance.disabled, + 'downloads_url': instance.downloadsUrl, + 'events_url': instance.eventsUrl, + 'fork': instance.fork, + 'forks_count': instance.forksCount, + 'forks_url': instance.forksUrl, + 'full_name': instance.fullName, + 'git_commits_url': instance.gitCommitsUrl, + 'git_refs_url': instance.gitRefsUrl, + 'git_tags_url': instance.gitTagsUrl, + 'git_url': instance.gitUrl, + 'has_downloads': instance.hasDownloads, + 'has_issues': instance.hasIssues, + 'has_pages': instance.hasPages, + 'has_projects': instance.hasProjects, + 'has_wiki': instance.hasWiki, + 'homepage': instance.homepage, + 'hooks_url': instance.hooksUrl, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'is_template': instance.isTemplate, + 'issue_comment_url': instance.issueCommentUrl, + 'issue_events_url': instance.issueEventsUrl, + 'issues_url': instance.issuesUrl, + 'keys_url': instance.keysUrl, + 'labels_url': instance.labelsUrl, + 'language': instance.language, + 'languages_url': instance.languagesUrl, + 'merge_commit_message': instance.mergeCommitMessage, + 'merge_commit_title': instance.mergeCommitTitle, + 'merges_url': instance.mergesUrl, + 'milestones_url': instance.milestonesUrl, + 'mirror_url': instance.mirrorUrl, + 'name': instance.name, + 'network_count': instance.networkCount, + 'node_id': instance.nodeId, + 'notifications_url': instance.notificationsUrl, + 'open_issues_count': instance.openIssuesCount, + 'owner': instance.owner, + 'permissions': instance.permissions, + 'private': instance.private, + 'pulls_url': instance.pullsUrl, + 'pushed_at': instance.pushedAt?.toIso8601String(), + 'releases_url': instance.releasesUrl, + 'size': instance.size, + 'squash_merge_commit_message': instance.squashMergeCommitMessage, + 'squash_merge_commit_title': instance.squashMergeCommitTitle, + 'ssh_url': instance.sshUrl, + 'stargazers_count': instance.stargazersCount, + 'stargazers_url': instance.stargazersUrl, + 'statuses_url': instance.statusesUrl, + 'subscribers_count': instance.subscribersCount, + 'subscribers_url': instance.subscribersUrl, + 'subscription_url': instance.subscriptionUrl, + 'svn_url': instance.svnUrl, + 'tags_url': instance.tagsUrl, + 'teams_url': instance.teamsUrl, + 'temp_clone_token': instance.tempCloneToken, + 'topics': instance.topics, + 'trees_url': instance.treesUrl, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'url': instance.url, + 'visibility': instance.visibility, + 'watchers_count': instance.watchersCount, + }; + +Owner _$OwnerFromJson(Map json) => Owner( + avatarUrl: json['avatar_url'] as String?, + eventsUrl: json['events_url'] as String?, + followersUrl: json['followers_url'] as String?, + followingUrl: json['following_url'] as String?, + gistsUrl: json['gists_url'] as String?, + gravatarId: json['gravatar_id'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + login: json['login'] as String?, + nodeId: json['node_id'] as String?, + organizationsUrl: json['organizations_url'] as String?, + receivedEventsUrl: json['received_events_url'] as String?, + reposUrl: json['repos_url'] as String?, + siteAdmin: json['site_admin'] as bool?, + starredUrl: json['starred_url'] as String?, + subscriptionsUrl: json['subscriptions_url'] as String?, + type: json['type'] as String?, + url: json['url'] as String?, + ); + +Map _$OwnerToJson(Owner instance) => { + 'avatar_url': instance.avatarUrl, + 'events_url': instance.eventsUrl, + 'followers_url': instance.followersUrl, + 'following_url': instance.followingUrl, + 'gists_url': instance.gistsUrl, + 'gravatar_id': instance.gravatarId, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'login': instance.login, + 'node_id': instance.nodeId, + 'organizations_url': instance.organizationsUrl, + 'received_events_url': instance.receivedEventsUrl, + 'repos_url': instance.reposUrl, + 'site_admin': instance.siteAdmin, + 'starred_url': instance.starredUrl, + 'subscriptions_url': instance.subscriptionsUrl, + 'type': instance.type, + 'url': instance.url, + }; + +Tree _$TreeFromJson(Map json) => Tree( + sha: json['sha'] as String?, + url: json['url'] as String?, + htmlUrl: json['html_url'] as String?, + ); + +Map _$TreeToJson(Tree instance) => { + 'sha': instance.sha, + 'url': instance.url, + 'html_url': instance.htmlUrl, + }; + +Verification _$VerificationFromJson(Map json) => Verification( + payload: json['payload'] as String?, + reason: json['reason'] as String?, + signature: json['signature'] as String?, + verified: json['verified'] as bool?, + ); + +Map _$VerificationToJson(Verification instance) => + { + 'payload': instance.payload, + 'reason': instance.reason, + 'signature': instance.signature, + 'verified': instance.verified, + }; + +HtmlLink _$HtmlLinkFromJson(Map json) => HtmlLink( + href: json['href'] as String?, + ); + +Map _$HtmlLinkToJson(HtmlLink instance) => { + 'href': instance.href, + }; + +PullRequestLink _$PullRequestLinkFromJson(Map json) => + PullRequestLink( + href: json['href'] as String?, + ); + +Map _$PullRequestLinkToJson(PullRequestLink instance) => + { + 'href': instance.href, + }; diff --git a/lib/src/common/model/users.dart b/lib/src/common/model/users.dart index 8044983a..f66fd395 100644 --- a/lib/src/common/model/users.dart +++ b/lib/src/common/model/users.dart @@ -24,6 +24,22 @@ class User { this.followingCount, this.createdAt, this.updatedAt, + + // Properties from the Timeline API + this.eventsUrl, + this.followersUrl, + this.followingUrl, + this.gistsUrl, + this.gravatarId, + this.nodeId, + this.organizationsUrl, + this.receivedEventsUrl, + this.reposUrl, + this.starredAt, + this.starredUrl, + this.subscriptionsUrl, + this.type, + this.url, }); @JsonKey(includeToJson: false, includeFromJson: false) @@ -90,6 +106,49 @@ class User { /// The username of the twitter account (without leading @) String? twitterUsername; + // The following properties were added to support the Timeline API. + + /// Example: `https://api.github.com/users/octocat/events{/privacy}` + String? eventsUrl; + + /// Example: `https://api.github.com/users/octocat/followers` + String? followersUrl; + + /// Example: `https://api.github.com/users/octocat/following{/other_user}` + String? followingUrl; + + /// Example: `https://api.github.com/users/octocat/gists{/gist_id}` + String? gistsUrl; + + /// Example: `41d064eb2195891e12d0413f63227ea7` + String? gravatarId; + + /// Example: `MDQ6VXNlcjE=` + String? nodeId; + + /// Example: `https://api.github.com/users/octocat/orgs` + String? organizationsUrl; + + /// Example: `https://api.github.com/users/octocat/received_events` + String? receivedEventsUrl; + + /// Example: `https://api.github.com/users/octocat/repos` + String? reposUrl; + + DateTime? starredAt; + + /// Example: `https://api.github.com/users/octocat/starred{/owner}{/repo}` + String? starredUrl; + + /// Example: `https://api.github.com/users/octocat/subscriptions` + String? subscriptionsUrl; + + /// Example: `User` + String? type; + + /// Example: `https://api.github.com/users/octocat` + String? url; + factory User.fromJson(Map input) => _$UserFromJson(input); Map toJson() => _$UserToJson(this); } @@ -98,13 +157,6 @@ class User { // https://developer.github.com/v3/repos/collaborators/#response @JsonSerializable() class Collaborator { - final String? login; - final int? id; - final String? htmlUrl; - final String? type; - final bool? siteAdmin; - final Map? permissions; - Collaborator( this.login, this.id, @@ -114,6 +166,13 @@ class Collaborator { this.permissions, ); + String? login; + int? id; + String? htmlUrl; + String? type; + bool? siteAdmin; + Map? permissions; + factory Collaborator.fromJson(Map json) => _$CollaboratorFromJson(json); Map toJson() => _$CollaboratorToJson(this); diff --git a/lib/src/common/model/users.g.dart b/lib/src/common/model/users.g.dart index 2713125a..1e61153c 100644 --- a/lib/src/common/model/users.g.dart +++ b/lib/src/common/model/users.g.dart @@ -7,7 +7,7 @@ part of 'users.dart'; // ************************************************************************** User _$UserFromJson(Map json) => User( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), login: json['login'] as String?, avatarUrl: json['avatar_url'] as String?, htmlUrl: json['html_url'] as String?, @@ -19,16 +19,32 @@ User _$UserFromJson(Map json) => User( email: json['email'] as String?, hirable: json['hirable'] as bool?, bio: json['bio'] as String?, - publicReposCount: json['public_repos'] as int?, - publicGistsCount: json['public_gists'] as int?, - followersCount: json['followers'] as int?, - followingCount: json['following'] as int?, + publicReposCount: (json['public_repos'] as num?)?.toInt(), + publicGistsCount: (json['public_gists'] as num?)?.toInt(), + followersCount: (json['followers'] as num?)?.toInt(), + followingCount: (json['following'] as num?)?.toInt(), createdAt: json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String), updatedAt: json['updated_at'] == null ? null : DateTime.parse(json['updated_at'] as String), + eventsUrl: json['events_url'] as String?, + followersUrl: json['followers_url'] as String?, + followingUrl: json['following_url'] as String?, + gistsUrl: json['gists_url'] as String?, + gravatarId: json['gravatar_id'] as String?, + nodeId: json['node_id'] as String?, + organizationsUrl: json['organizations_url'] as String?, + receivedEventsUrl: json['received_events_url'] as String?, + reposUrl: json['repos_url'] as String?, + starredAt: json['starred_at'] == null + ? null + : DateTime.parse(json['starred_at'] as String), + starredUrl: json['starred_url'] as String?, + subscriptionsUrl: json['subscriptions_url'] as String?, + type: json['type'] as String?, + url: json['url'] as String?, )..twitterUsername = json['twitter_username'] as String?; Map _$UserToJson(User instance) => { @@ -51,11 +67,25 @@ Map _$UserToJson(User instance) => { 'created_at': instance.createdAt?.toIso8601String(), 'updated_at': instance.updatedAt?.toIso8601String(), 'twitter_username': instance.twitterUsername, + 'events_url': instance.eventsUrl, + 'followers_url': instance.followersUrl, + 'following_url': instance.followingUrl, + 'gists_url': instance.gistsUrl, + 'gravatar_id': instance.gravatarId, + 'node_id': instance.nodeId, + 'organizations_url': instance.organizationsUrl, + 'received_events_url': instance.receivedEventsUrl, + 'repos_url': instance.reposUrl, + 'starred_at': instance.starredAt?.toIso8601String(), + 'starred_url': instance.starredUrl, + 'subscriptions_url': instance.subscriptionsUrl, + 'type': instance.type, + 'url': instance.url, }; Collaborator _$CollaboratorFromJson(Map json) => Collaborator( json['login'] as String?, - json['id'] as int?, + (json['id'] as num?)?.toInt(), json['html_url'] as String?, json['type'] as String?, json['site_admin'] as bool?, @@ -75,13 +105,13 @@ Map _$CollaboratorToJson(Collaborator instance) => }; Contributor _$ContributorFromJson(Map json) => Contributor( - id: json['id'] as int?, + id: (json['id'] as num?)?.toInt(), login: json['login'] as String?, avatarUrl: json['avatar_url'] as String?, htmlUrl: json['html_url'] as String?, type: json['type'] as String?, siteAdmin: json['site_admin'] as bool?, - contributions: json['contributions'] as int?, + contributions: (json['contributions'] as num?)?.toInt(), ); Map _$ContributorToJson(Contributor instance) => @@ -97,7 +127,7 @@ Map _$ContributorToJson(Contributor instance) => CurrentUser _$CurrentUserFromJson(Map json) => CurrentUser() ..login = json['login'] as String? - ..id = json['id'] as int? + ..id = (json['id'] as num?)?.toInt() ..avatarUrl = json['avatar_url'] as String? ..htmlUrl = json['html_url'] as String? ..siteAdmin = json['site_admin'] as bool? @@ -108,10 +138,10 @@ CurrentUser _$CurrentUserFromJson(Map json) => CurrentUser() ..email = json['email'] as String? ..hirable = json['hirable'] as bool? ..bio = json['bio'] as String? - ..publicReposCount = json['public_repos'] as int? - ..publicGistsCount = json['public_gists'] as int? - ..followersCount = json['followers'] as int? - ..followingCount = json['following'] as int? + ..publicReposCount = (json['public_repos'] as num?)?.toInt() + ..publicGistsCount = (json['public_gists'] as num?)?.toInt() + ..followersCount = (json['followers'] as num?)?.toInt() + ..followingCount = (json['following'] as num?)?.toInt() ..createdAt = json['created_at'] == null ? null : DateTime.parse(json['created_at'] as String) @@ -119,9 +149,25 @@ CurrentUser _$CurrentUserFromJson(Map json) => CurrentUser() ? null : DateTime.parse(json['updated_at'] as String) ..twitterUsername = json['twitter_username'] as String? - ..privateReposCount = json['total_private_repos'] as int? - ..ownedPrivateReposCount = json['owned_private_repos'] as int? - ..diskUsage = json['disk_usage'] as int? + ..eventsUrl = json['events_url'] as String? + ..followersUrl = json['followers_url'] as String? + ..followingUrl = json['following_url'] as String? + ..gistsUrl = json['gists_url'] as String? + ..gravatarId = json['gravatar_id'] as String? + ..nodeId = json['node_id'] as String? + ..organizationsUrl = json['organizations_url'] as String? + ..receivedEventsUrl = json['received_events_url'] as String? + ..reposUrl = json['repos_url'] as String? + ..starredAt = json['starred_at'] == null + ? null + : DateTime.parse(json['starred_at'] as String) + ..starredUrl = json['starred_url'] as String? + ..subscriptionsUrl = json['subscriptions_url'] as String? + ..type = json['type'] as String? + ..url = json['url'] as String? + ..privateReposCount = (json['total_private_repos'] as num?)?.toInt() + ..ownedPrivateReposCount = (json['owned_private_repos'] as num?)?.toInt() + ..diskUsage = (json['disk_usage'] as num?)?.toInt() ..plan = json['plan'] == null ? null : UserPlan.fromJson(json['plan'] as Map); @@ -147,6 +193,20 @@ Map _$CurrentUserToJson(CurrentUser instance) => 'created_at': instance.createdAt?.toIso8601String(), 'updated_at': instance.updatedAt?.toIso8601String(), 'twitter_username': instance.twitterUsername, + 'events_url': instance.eventsUrl, + 'followers_url': instance.followersUrl, + 'following_url': instance.followingUrl, + 'gists_url': instance.gistsUrl, + 'gravatar_id': instance.gravatarId, + 'node_id': instance.nodeId, + 'organizations_url': instance.organizationsUrl, + 'received_events_url': instance.receivedEventsUrl, + 'repos_url': instance.reposUrl, + 'starred_at': instance.starredAt?.toIso8601String(), + 'starred_url': instance.starredUrl, + 'subscriptions_url': instance.subscriptionsUrl, + 'type': instance.type, + 'url': instance.url, 'total_private_repos': instance.privateReposCount, 'owned_private_repos': instance.ownedPrivateReposCount, 'disk_usage': instance.diskUsage, @@ -155,9 +215,9 @@ Map _$CurrentUserToJson(CurrentUser instance) => UserPlan _$UserPlanFromJson(Map json) => UserPlan() ..name = json['name'] as String? - ..space = json['space'] as int? - ..privateReposCount = json['private_repos'] as int? - ..collaboratorsCount = json['collaborators'] as int?; + ..space = (json['space'] as num?)?.toInt() + ..privateReposCount = (json['private_repos'] as num?)?.toInt() + ..collaboratorsCount = (json['collaborators'] as num?)?.toInt(); Map _$UserPlanToJson(UserPlan instance) => { 'name': instance.name, diff --git a/lib/src/common/orgs_service.dart b/lib/src/common/orgs_service.dart index c0774bda..b2ca7b26 100644 --- a/lib/src/common/orgs_service.dart +++ b/lib/src/common/orgs_service.dart @@ -9,7 +9,7 @@ import 'package:http/http.dart' as http; /// /// API docs: https://developer.github.com/v3/orgs/ class OrganizationsService extends Service { - OrganizationsService(GitHub github) : super(github); + OrganizationsService(super.github); /// Lists all of the memberships in organizations for the given [userName]. /// If [userName] is not specified we list the memberships in organizations @@ -21,8 +21,11 @@ class OrganizationsService extends Service { if (userName == null) { requestPath = '/user/orgs'; } - return PaginationHelper(github) - .objects('GET', requestPath, Organization.fromJson); + return PaginationHelper(github).objects( + 'GET', + requestPath, + Organization.fromJson, + ); } /// Fetches the organization specified by [name]. @@ -31,7 +34,7 @@ class OrganizationsService extends Service { Future get(String? name) => github.getJSON('/orgs/$name', convert: Organization.fromJson, statusCode: StatusCodes.OK, fail: (http.Response response) { - if (response.statusCode == 404) { + if (response.statusCode == StatusCodes.NOT_FOUND) { throw OrganizationNotFound(github, name); } }); @@ -47,13 +50,15 @@ class OrganizationsService extends Service { /// Edits an Organization /// /// API docs: https://developer.github.com/v3/orgs/#edit-an-organization - Future edit(String org, - {String? billingEmail, - String? company, - String? email, - String? location, - String? name, - String? description}) { + Future edit( + String org, { + String? billingEmail, + String? company, + String? email, + String? location, + String? name, + String? description, + }) { final map = createNonNullMap({ 'billing_email': billingEmail, 'company': company, @@ -64,7 +69,7 @@ class OrganizationsService extends Service { }); return github.postJSON('/orgs/$org', - statusCode: 200, + statusCode: StatusCodes.OK, convert: Organization.fromJson, body: GitHubJson.encode(map)); } @@ -73,8 +78,11 @@ class OrganizationsService extends Service { /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-teams Stream listTeams(String orgName) { - return PaginationHelper(github) - .objects('GET', '/orgs/$orgName/teams', Team.fromJson); + return PaginationHelper(github).objects( + 'GET', + '/orgs/$orgName/teams', + Team.fromJson, + ); } /// Gets the team specified by the [teamId]. @@ -82,14 +90,34 @@ class OrganizationsService extends Service { /// API docs: https://developer.github.com/v3/orgs/teams/#get-team Future getTeam(int teamId) { return github.getJSON('/teams/$teamId', - convert: Organization.fromJson, statusCode: 200) as Future; + convert: Organization.fromJson, + statusCode: StatusCodes.OK) as Future; + } + + /// Gets the team specified by its [teamName]. + /// + /// https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#get-a-team-by-name + Future getTeamByName( + String orgName, + String teamName, + ) { + return github.getJSON( + 'orgs/$orgName/teams/$teamName', + convert: Team.fromJson, + statusCode: StatusCodes.OK, + ); } /// Creates a Team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#create-team - Future createTeam(String org, String name, - {String? description, List? repos, String? permission}) { + Future createTeam( + String org, + String name, { + String? description, + List? repos, + String? permission, + }) { final map = createNonNullMap({ 'name': name, 'description': description, @@ -98,14 +126,20 @@ class OrganizationsService extends Service { }); return github.postJSON('/orgs/$org/teams', - statusCode: 201, convert: Team.fromJson, body: GitHubJson.encode(map)); + statusCode: StatusCodes.CREATED, + convert: Team.fromJson, + body: GitHubJson.encode(map)); } /// Edits a Team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#edit-team - Future editTeam(int teamId, String name, - {String? description, String? permission}) { + Future editTeam( + int teamId, + String name, { + String? description, + String? permission, + }) { final map = createNonNullMap({ 'name': name, 'description': description, @@ -114,7 +148,7 @@ class OrganizationsService extends Service { return github.postJSON( '/teams/$teamId', - statusCode: 200, + statusCode: StatusCodes.OK, convert: Team.fromJson, body: GitHubJson.encode(map), ); @@ -125,7 +159,7 @@ class OrganizationsService extends Service { /// API docs: https://developer.github.com/v3/orgs/teams/#delete-team Future deleteTeam(int teamId) { return github.request('DELETE', '/teams/$teamId').then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } @@ -133,8 +167,11 @@ class OrganizationsService extends Service { /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-team-members Stream listTeamMembers(int teamId) { - return PaginationHelper(github) - .objects('GET', '/teams/$teamId/members', TeamMember.fromJson); + return PaginationHelper(github).objects( + 'GET', + '/teams/$teamId/members', + TeamMember.fromJson, + ); } Future getTeamMemberStatus(int teamId, String user) { @@ -143,85 +180,129 @@ class OrganizationsService extends Service { }); } - /// Returns the membership status for a user in a team. + /// Returns the membership status for a [user] in a team with [teamId]. /// /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership - Future getTeamMembership(int teamId, String user) { - final completer = Completer(); - - github - .getJSON( - '/teams/$teamId/memberships/$user', - statusCode: 200, - fail: (http.Response response) { - if (response.statusCode == 404) { - completer.complete(TeamMembershipState(null)); - } else { - github.handleStatusCode(response); - } - }, - convert: (dynamic json) => TeamMembershipState(json['state']), - ) - .then(completer.complete); + Future getTeamMembership( + int teamId, + String user, + ) { + return github.getJSON( + '/teams/$teamId/memberships/$user', + statusCode: StatusCodes.OK, + convert: (dynamic json) => TeamMembershipState( + json['state'], + ), + ); + } - return completer.future; + /// Returns the membership status for [user] in [teamName] given the [orgName]. + /// + /// Note that this will throw on NotFound if the user is not a member of the + /// team. Adding a fail function to set the value does not help unless you + /// throw out of the fail function. + Future getTeamMembershipByName( + String orgName, + String teamName, + String user, + ) { + return github.getJSON( + '/orgs/$orgName/teams/$teamName/memberships/$user', + statusCode: StatusCodes.OK, + convert: (dynamic json) => TeamMembershipState( + json['state'], + ), + ); } /// Invites a user to the specified team. /// /// API docs: https://developer.github.com/v3/teams/members/#add-or-update-team-membership - Future addTeamMembership(int teamId, String user) async { - final response = await github - .request('PUT', '/teams/$teamId/memberships/$user', statusCode: 200); + Future addTeamMembership( + int teamId, + String user, + ) async { + final response = await github.request( + 'PUT', + '/teams/$teamId/memberships/$user', + statusCode: StatusCodes.OK, + ); return TeamMembershipState(jsonDecode(response.body)['state']); } /// Removes a user from the specified team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership - Future removeTeamMembership(int teamId, String user) { - return github.request('DELETE', '/teams/$teamId/memberships/$user', - statusCode: 204); + Future removeTeamMembership( + int teamId, + String user, + ) { + return github.request( + 'DELETE', + '/teams/$teamId/memberships/$user', + statusCode: StatusCodes.NO_CONTENT, + ); } /// Lists the repositories that the specified team has access to. /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-team-repos Stream listTeamRepositories(int teamId) { - return PaginationHelper(github) - .objects('GET', '/teams/$teamId/repos', Repository.fromJson); + return PaginationHelper(github).objects( + 'GET', + '/teams/$teamId/repos', + Repository.fromJson, + ); } /// Checks if a team manages the specified repository. /// /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-repo - Future isTeamRepository(int teamId, RepositorySlug slug) { + Future isTeamRepository( + int teamId, + RepositorySlug slug, + ) { return github - .request('GET', '/teams/$teamId/repos/${slug.fullName}') + .request( + 'GET', + '/teams/$teamId/repos/${slug.fullName}', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } /// Adds a repository to be managed by the specified team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#add-team-repo - Future addTeamRepository(int teamId, RepositorySlug slug) { + Future addTeamRepository( + int teamId, + RepositorySlug slug, + ) { return github - .request('PUT', '/teams/$teamId/repos/${slug.fullName}') + .request( + 'PUT', + '/teams/$teamId/repos/${slug.fullName}', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } /// Removes a repository from being managed by the specified team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#remove-team-repo - Future removeTeamRepository(int teamId, RepositorySlug slug) { + Future removeTeamRepository( + int teamId, + RepositorySlug slug, + ) { return github - .request('DELETE', '/teams/$teamId/repos/${slug.fullName}') + .request( + 'DELETE', + '/teams/$teamId/repos/${slug.fullName}', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } @@ -229,16 +310,22 @@ class OrganizationsService extends Service { /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams Stream listUserTeams() { - return PaginationHelper(github) - .objects('GET', '/user/teams', Team.fromJson); + return PaginationHelper(github).objects( + 'GET', + '/user/teams', + Team.fromJson, + ); } /// Lists all of the users in an organization /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams Stream listUsers(String org) { - return PaginationHelper(github) - .objects('GET', '/orgs/$org/members', User.fromJson); + return PaginationHelper(github).objects( + 'GET', + '/orgs/$org/members', + User.fromJson, + ); } /// Lists the hooks for the specified organization. @@ -252,14 +339,20 @@ class OrganizationsService extends Service { /// Fetches a single hook by [id]. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#get-single-hook - Future getHook(String org, int id) => + Future getHook( + String org, + int id, + ) => github.getJSON('/orgs/$org/hooks/$id', convert: (dynamic i) => Hook.fromJson(i)..repoName = org); /// Creates an organization hook based on the specified [hook]. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#create-a-hook - Future createHook(String org, CreateHook hook) { + Future createHook( + String org, + CreateHook hook, + ) { return github.postJSON('/orgs/$org/hooks', convert: (Map i) => Hook.fromJson(i)..repoName = org, body: GitHubJson.encode(hook)); @@ -270,16 +363,27 @@ class OrganizationsService extends Service { /// Pings the organization hook. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#ping-a-hook - Future pingHook(String org, int id) { + Future pingHook( + String org, + int id, + ) { return github - .request('POST', '/orgs/$org/hooks/$id/pings') - .then((response) => response.statusCode == 204); + .request( + 'POST', + '/orgs/$org/hooks/$id/pings', + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } /// Deletes the specified hook. Future deleteHook(String org, int id) { - return github.request('DELETE', '/orgs/$org/hooks/$id').then((response) { - return response.statusCode == 204; + return github + .request( + 'DELETE', + '/orgs/$org/hooks/$id', + ) + .then((response) { + return response.statusCode == StatusCodes.NO_CONTENT; }); } } diff --git a/lib/src/common/pulls_service.dart b/lib/src/common/pulls_service.dart index 5242f0e3..7fee2bf9 100644 --- a/lib/src/common/pulls_service.dart +++ b/lib/src/common/pulls_service.dart @@ -8,7 +8,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/pulls/ class PullRequestsService extends Service { - PullRequestsService(GitHub github) : super(github); + PullRequestsService(super.github); /// Fetches several pull requests. /// diff --git a/lib/src/common/repos_service.dart b/lib/src/common/repos_service.dart index 535e1060..83fa5ef0 100644 --- a/lib/src/common/repos_service.dart +++ b/lib/src/common/repos_service.dart @@ -9,7 +9,7 @@ import 'package:http/http.dart' as http; /// /// API docs: https://developer.github.com/v3/repos/ class RepositoriesService extends Service { - RepositoriesService(GitHub github) : super(github); + RepositoriesService(super.github); /// Lists the repositories of the currently authenticated user. /// @@ -448,14 +448,41 @@ class RepositoriesService extends Service { /// Lists the commits of the provided repository [slug]. /// + /// [sha] is the SHA or branch to start listing commits from. Default: the + /// repository’s default branch (usually main). + /// + /// [path] will only show commits that changed that file path. + /// + /// [author] and [committer] are the GitHub username to filter commits for. + /// + /// [since] shows commit after this time, and [until] shows commits before + /// this time. + /// /// API docs: https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository - Stream listCommits(RepositorySlug slug) { + Stream listCommits( + RepositorySlug slug, { + String? sha, + String? path, + String? author, + String? committer, + DateTime? since, + DateTime? until, + }) { ArgumentError.checkNotNull(slug); + final params = { + if (author != null) 'author': author, + if (committer != null) 'committer': committer, + if (sha != null) 'sha': sha, + if (path != null) 'path': path, + if (since != null) 'since': since.toIso8601String(), + if (until != null) 'until': until.toIso8601String(), + }; return PaginationHelper(github) .objects, RepositoryCommit>( 'GET', '/repos/${slug.fullName}/commits', RepositoryCommit.fromJson, + params: params, ); } diff --git a/lib/src/common/search_service.dart b/lib/src/common/search_service.dart index bf2b41ec..27cf6eef 100644 --- a/lib/src/common/search_service.dart +++ b/lib/src/common/search_service.dart @@ -8,7 +8,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://developer.github.com/v3/search/ class SearchService extends Service { - SearchService(GitHub github) : super(github); + SearchService(super.github); /// Search for repositories using [query]. /// Since the Search Rate Limit is small, this is a best effort implementation. diff --git a/lib/src/common/url_shortener_service.dart b/lib/src/common/url_shortener_service.dart index 0b1e296f..60db2958 100644 --- a/lib/src/common/url_shortener_service.dart +++ b/lib/src/common/url_shortener_service.dart @@ -6,7 +6,7 @@ import 'package:github/src/common.dart'; /// /// API docs: https://github.com/blog/985-git-io-github-url-shortener class UrlShortenerService extends Service { - UrlShortenerService(GitHub github) : super(github); + UrlShortenerService(super.github); /// Shortens the provided [url]. An optional [code] can be provided to create /// your own vanity URL. diff --git a/lib/src/common/users_service.dart b/lib/src/common/users_service.dart index bb27ff6d..6485f4b0 100644 --- a/lib/src/common/users_service.dart +++ b/lib/src/common/users_service.dart @@ -8,7 +8,7 @@ import 'package:http/http.dart' as http; /// /// API docs: https://developer.github.com/v3/users/ class UsersService extends Service { - UsersService(GitHub github) : super(github); + UsersService(super.github); /// Fetches the user specified by [name]. /// diff --git a/lib/src/common/util/auth.dart b/lib/src/common/util/auth.dart index 925ffc93..1c4d6798 100644 --- a/lib/src/common/util/auth.dart +++ b/lib/src/common/util/auth.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + /// Authentication information. class Authentication { /// OAuth2 Token @@ -9,26 +11,65 @@ class Authentication { /// GitHub Password final String? password; + final String? bearerToken; + + // TODO: mark the pram as `String` to REQUIRE a non-null value. + // NEXT major version /// Creates an [Authentication] instance that uses the specified OAuth2 [token]. - Authentication.withToken(this.token) + const Authentication.withToken(this.token) : username = null, - password = null; + password = null, + bearerToken = null; + + /// Creates an [Authentication] instance that uses the specified + /// [bearerToken]. + const Authentication.bearerToken(String this.bearerToken) + : username = null, + password = null, + token = null; /// Creates an [Authentication] instance that has no authentication. - Authentication.anonymous() + const Authentication.anonymous() : token = null, username = null, - password = null; + password = null, + bearerToken = null; + // TODO: mark the `username` and `password` params as `String` to REQUIRE + // non-null values. - NEXT major version /// Creates an [Authentication] instance that uses a username and password. - Authentication.basic(this.username, this.password) : token = null; + const Authentication.basic(this.username, this.password) + : token = null, + bearerToken = null; /// Anonymous Authentication Flag - bool get isAnonymous => !isBasic && !isToken; + bool get isAnonymous => !isBasic && !isToken && !isBearer; /// Basic Authentication Flag bool get isBasic => username != null; /// Token Authentication Flag bool get isToken => token != null; + + // This instance represents a authentication with a "Bearer" token. + bool get isBearer => bearerToken != null; + + /// Returns a value for the `Authorization` HTTP request header or `null` + /// if [isAnonymous] is `true`. + String? authorizationHeaderValue() { + if (isToken) { + return 'token $token'; + } + + if (isBasic) { + final userAndPass = base64Encode(utf8.encode('$username:$password')); + return 'basic $userAndPass'; + } + + if (isBearer) { + return 'Bearer $bearerToken'; + } + + return null; + } } diff --git a/lib/src/common/util/errors.dart b/lib/src/common/util/errors.dart index 81fa72b2..14625a6c 100644 --- a/lib/src/common/util/errors.dart +++ b/lib/src/common/util/errors.dart @@ -25,14 +25,13 @@ class NotReady extends GitHubError { /// GitHub Entity was not found class NotFound extends GitHubError { const NotFound( - GitHub github, - String msg, - ) : super(github, msg); + super.github, + String super.msg, + ); } class BadRequest extends GitHubError { - const BadRequest(GitHub github, [String? msg = 'Not Found']) - : super(github, msg); + const BadRequest(super.github, [super.msg = 'Not Found']); } /// GitHub Repository was not found @@ -94,11 +93,10 @@ class NotAuthenticated extends GitHubError { } class InvalidJSON extends BadRequest { - const InvalidJSON(GitHub github, [String? message = 'Invalid JSON']) - : super(github, message); + const InvalidJSON(super.github, [super.message = 'Invalid JSON']); } class ValidationFailed extends GitHubError { - const ValidationFailed(GitHub github, [String message = 'Validation Failed']) - : super(github, message); + const ValidationFailed(super.github, + [String super.message = 'Validation Failed']); } diff --git a/lib/src/common/util/pagination.dart b/lib/src/common/util/pagination.dart index 95d47107..93f3a0d7 100644 --- a/lib/src/common/util/pagination.dart +++ b/lib/src/common/util/pagination.dart @@ -28,10 +28,6 @@ class PaginationHelper { params = Map.from(params); } - var page = params['page'] ?? 1; - params['page'] = page; - - // ignore: literal_only_boolean_expressions while (true) { http.Response response; try { @@ -71,7 +67,8 @@ class PaginationHelper { break; } - params['page'] = ++page; + path = next; + params = null; } } diff --git a/lib/src/common/util/utils.dart b/lib/src/common/util/utils.dart index 57bd6712..5c690774 100644 --- a/lib/src/common/util/utils.dart +++ b/lib/src/common/util/utils.dart @@ -21,7 +21,7 @@ abstract class EnumWithValue { /// True iff [other] is an [EnumWithValue] with the same value as this object. @override - bool operator ==(dynamic other) => + bool operator ==(Object other) => other is EnumWithValue && value == other.value; @override diff --git a/lib/src/common/xplat_common.dart b/lib/src/common/xplat_common.dart index cc0bfcbb..1c60f906 100644 --- a/lib/src/common/xplat_common.dart +++ b/lib/src/common/xplat_common.dart @@ -10,7 +10,7 @@ import 'package:github/src/common.dart'; /// In both contexts it delegates to [findAuthenticationInMap] to find the /// github token or username and password. Authentication findAuthenticationFromEnvironment() => - Authentication.anonymous(); + const Authentication.anonymous(); /// Checks the passed in map for keys in [COMMON_GITHUB_TOKEN_ENV_KEYS]. /// The first one that exists is used as the github token to call [Authentication.withToken] with. diff --git a/lib/src/server/hooks.dart b/lib/src/server/hooks.dart index ea250c48..ae3fa0bf 100644 --- a/lib/src/server/hooks.dart +++ b/lib/src/server/hooks.dart @@ -1,8 +1,11 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; + import 'package:json_annotation/json_annotation.dart'; + import '../common.dart'; +import '../common/model/changes.dart'; part 'hooks.g.dart'; @@ -203,12 +206,14 @@ class PullRequestEvent extends HookEvent { this.pullRequest, this.sender, this.repository, + this.changes, }); String? action; int? number; PullRequest? pullRequest; User? sender; Repository? repository; + Changes? changes; factory PullRequestEvent.fromJson(Map input) => _$PullRequestEventFromJson(input); diff --git a/lib/src/server/hooks.g.dart b/lib/src/server/hooks.g.dart index 3babd5cf..81cbb790 100644 --- a/lib/src/server/hooks.g.dart +++ b/lib/src/server/hooks.g.dart @@ -132,7 +132,7 @@ Map _$IssueEventToJson(IssueEvent instance) => PullRequestEvent _$PullRequestEventFromJson(Map json) => PullRequestEvent( action: json['action'] as String?, - number: json['number'] as int?, + number: (json['number'] as num?)?.toInt(), pullRequest: json['pull_request'] == null ? null : PullRequest.fromJson(json['pull_request'] as Map), @@ -142,6 +142,9 @@ PullRequestEvent _$PullRequestEventFromJson(Map json) => repository: json['repository'] == null ? null : Repository.fromJson(json['repository'] as Map), + changes: json['changes'] == null + ? null + : Changes.fromJson(json['changes'] as Map), ); Map _$PullRequestEventToJson(PullRequestEvent instance) => @@ -151,6 +154,7 @@ Map _$PullRequestEventToJson(PullRequestEvent instance) => 'pull_request': instance.pullRequest, 'sender': instance.sender, 'repository': instance.repository, + 'changes': instance.changes, }; CreateEvent _$CreateEventFromJson(Map json) => CreateEvent( diff --git a/lib/src/server/xplat_server.dart b/lib/src/server/xplat_server.dart index 3bd6e100..a6c85d02 100644 --- a/lib/src/server/xplat_server.dart +++ b/lib/src/server/xplat_server.dart @@ -27,5 +27,5 @@ Authentication findAuthenticationFromEnvironment() { } return findAuthenticationInMap(Platform.environment) ?? - Authentication.anonymous(); + const Authentication.anonymous(); } diff --git a/pubspec.yaml b/pubspec.yaml index afd47919..eed1fec4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,28 +1,27 @@ name: github -version: 9.10.1 +version: 9.25.0 description: A high-level GitHub API Client Library that uses Github's v3 API homepage: https://github.com/SpinlockLabs/github.dart environment: - sdk: '>=2.18.0 <3.0.0' + sdk: ^3.5.0 dependencies: - http: ^0.13.0 + http: ^1.0.0 http_parser: ^4.0.0 - json_annotation: ^4.8.0 - meta: ^1.3.0 + json_annotation: ^4.9.0 + meta: ^1.7.0 dev_dependencies: - build_runner: any - build_test: any - build_web_compilers: any + build_runner: ^2.2.1 + build_test: ^2.1.2 + build_web_compilers: '>=3.2.6 < 5.0.0' collection: ^1.15.0 - dependency_validator: + dependency_validator: ^3.0.0 json_serializable: ^6.6.1 - lints: ^2.0.0 - mockito: ^5.0.0 - nock: ^1.0.0 + lints: ^5.0.0 + nock: ^1.1.3 pub_semver: ^2.0.0 - test: ^1.16.0 - yaml: ^3.0.0 - yaml_edit: + test: ^1.21.6 + yaml: ^3.1.0 + yaml_edit: ^2.2.0 diff --git a/test/common/data/repos_json.dart b/test/common/data/repos_json.dart new file mode 100644 index 00000000..f5087cf7 --- /dev/null +++ b/test/common/data/repos_json.dart @@ -0,0 +1,81 @@ +const String listCommits = ''' +[ + { + "sha": "3771b3c9ab1912d32a0d0baffaf489d561caf558", + "node_id": "C_kwDOAVT0d9oAKDM3NzFiM2M5YWIxOTEyZDMyYTBkMGJhZmZhZjQ4OWQ1NjFjYWY1NTg", + "commit": { + "author": { + "name": "Rob Becker", + "email": "rob.becker@workiva.com", + "date": "2023-04-17T14:55:14Z" + }, + "committer": { + "name": "Rob Becker", + "email": "rob.becker@workiva.com", + "date": "2023-04-17T14:55:14Z" + }, + "message": "prep 9.12.0", + "tree": { + "sha": "282532b41e8fead81ec6d68e7e603139e7dd7581", + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees/282532b41e8fead81ec6d68e7e603139e7dd7581" + }, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits/3771b3c9ab1912d32a0d0baffaf489d561caf558", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid" + } + }, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits/3771b3c9ab1912d32a0d0baffaf489d561caf558", + "html_url": "https://github.com/SpinlockLabs/github.dart/commit/3771b3c9ab1912d32a0d0baffaf489d561caf558", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits/3771b3c9ab1912d32a0d0baffaf489d561caf558/comments", + "author": { + "login": "robbecker-wf", + "id": 6053699, + "node_id": "MDQ6VXNlcjYwNTM2OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/6053699?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/robbecker-wf", + "html_url": "https://github.com/robbecker-wf", + "followers_url": "https://api.github.com/users/robbecker-wf/followers", + "following_url": "https://api.github.com/users/robbecker-wf/following{/other_user}", + "gists_url": "https://api.github.com/users/robbecker-wf/gists{/gist_id}", + "starred_url": "https://api.github.com/users/robbecker-wf/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/robbecker-wf/subscriptions", + "organizations_url": "https://api.github.com/users/robbecker-wf/orgs", + "repos_url": "https://api.github.com/users/robbecker-wf/repos", + "events_url": "https://api.github.com/users/robbecker-wf/events{/privacy}", + "received_events_url": "https://api.github.com/users/robbecker-wf/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "robbecker-wf", + "id": 6053699, + "node_id": "MDQ6VXNlcjYwNTM2OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/6053699?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/robbecker-wf", + "html_url": "https://github.com/robbecker-wf", + "followers_url": "https://api.github.com/users/robbecker-wf/followers", + "following_url": "https://api.github.com/users/robbecker-wf/following{/other_user}", + "gists_url": "https://api.github.com/users/robbecker-wf/gists{/gist_id}", + "starred_url": "https://api.github.com/users/robbecker-wf/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/robbecker-wf/subscriptions", + "organizations_url": "https://api.github.com/users/robbecker-wf/orgs", + "repos_url": "https://api.github.com/users/robbecker-wf/repos", + "events_url": "https://api.github.com/users/robbecker-wf/events{/privacy}", + "received_events_url": "https://api.github.com/users/robbecker-wf/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "a3081681da68383d9a5f18ef3502f47f7144e7d8", + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits/a3081681da68383d9a5f18ef3502f47f7144e7d8", + "html_url": "https://github.com/SpinlockLabs/github.dart/commit/a3081681da68383d9a5f18ef3502f47f7144e7d8" + } + ] + } +] +'''; diff --git a/test/common/github_test.dart b/test/common/github_test.dart index 4f7d6727..97ce2930 100644 --- a/test/common/github_test.dart +++ b/test/common/github_test.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:github/src/common/github.dart'; +import 'package:github/src/common.dart'; import 'package:http/http.dart'; import 'package:http/testing.dart'; import 'package:test/test.dart'; @@ -38,5 +38,22 @@ void main() { final userAgent = request!.headers['User-Agent']; expect(userAgent, 'github.dart'); }); + + test('anonymous auth passes no authorization header', () async { + Request? request; + final client = MockClient((r) async { + request = r; + return Response('{}', HttpStatus.ok); + }); + + final github = GitHub( + client: client, + auth: const Authentication.anonymous(), + ); + await github.getJSON(''); // Make HTTP request + + expect(request, isNotNull); + expect(request!.headers.containsKey('Authorization'), isFalse); + }); }); } diff --git a/test/common/misc_service_test.dart b/test/common/misc_service_test.dart new file mode 100644 index 00000000..2066e271 --- /dev/null +++ b/test/common/misc_service_test.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +void main() { + MiscService create(Future Function(Request) f) { + final client = MockClient(f); + final github = GitHub(client: client); + return MiscService(github); + } + + test('api status', () async { + final miscService = create( + (_) async => Response(''' +{ + "page":{ + "id":"kctbh9vrtdwd", + "name":"GitHub", + "url":"https://www.githubstatus.com", + "updated_at": "2023-11-29T08:03:04Z" + }, + "status": { + "description": "Partial System Outage", + "indicator": "major" + } +}''', HttpStatus.ok), + ); + final status = await miscService.getApiStatus(); + expect(status.page, isNotNull); + expect(status.page?.id, 'kctbh9vrtdwd'); + expect(status.status, isNotNull); + expect(status.status?.indicator, 'major'); + }); +} diff --git a/test/common/repos_service_test.dart b/test/common/repos_service_test.dart new file mode 100644 index 00000000..2f3f9120 --- /dev/null +++ b/test/common/repos_service_test.dart @@ -0,0 +1,49 @@ +import 'package:github/src/common.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +import 'data/repos_json.dart'; + +void main() { + final slug = RepositorySlug('SpinlockLabs', 'github.dart'); + RepositoriesService create(Future Function(Request) f) { + final client = MockClient(f); + final github = GitHub(client: client); + return RepositoriesService(github); + } + + test('listCommits', () async { + final repositories = create((request) async { + expect(request.url.path, '/repos/${slug.fullName}/commits'); + expect(request.url.query, isEmpty); + + return Response(listCommits, StatusCodes.OK); + }); + final commits = await repositories.listCommits(slug).toList(); + expect(commits, hasLength(1)); + }); + + test('listCommits with query params', () async { + final repositories = create((request) async { + expect(request.url.path, '/repos/${slug.fullName}/commits'); + expect( + request.url.query, + 'author=octocat&committer=octodog&sha=abc&path=%2Fpath&since=2022-02-22T00%3A00%3A00.000&until=2023-02-22T00%3A00%3A00.000', + ); + return Response(listCommits, StatusCodes.OK); + }); + final commits = await repositories + .listCommits( + slug, + sha: 'abc', + path: '/path', + author: 'octocat', + committer: 'octodog', + since: DateTime(2022, 2, 22), + until: DateTime(2023, 2, 22), + ) + .toList(); + expect(commits, hasLength(1)); + }); +} diff --git a/test/experiment/crawler.dart b/test/experiment/crawler.dart index cc4e23ee..7dd8e737 100644 --- a/test/experiment/crawler.dart +++ b/test/experiment/crawler.dart @@ -1,7 +1,7 @@ import 'package:github/github.dart'; void main() { - final github = GitHub(auth: Authentication.anonymous()); + final github = GitHub(auth: const Authentication.anonymous()); final crawler = RepositoryCrawler( github, diff --git a/test/git_test.dart b/test/git_test.dart index 34487b95..e95ef5d6 100644 --- a/test/git_test.dart +++ b/test/git_test.dart @@ -10,8 +10,8 @@ const date = '2014-10-02T15:21:29Z'; GitHub createGithub() { return GitHub( endpoint: fakeApiUrl, - auth: - Authentication.withToken('0000000000000000000000000000000000000001')); + auth: const Authentication.withToken( + '0000000000000000000000000000000000000001')); } void main() { @@ -172,7 +172,7 @@ void main() { test('code search', () async { nock(fakeApiUrl) .get( - '/search/code?q=search%20repo%3ASpinlockLabs%2Fgithub.dart%20in%3Afile&per_page=20&page=1') + '/search/code?q=search%20repo%3ASpinlockLabs%2Fgithub.dart%20in%3Afile&per_page=20') .reply(200, nocked.searchResults); final results = (await github.search diff --git a/test/helper/http.dart b/test/helper/http.dart index 2c5eaab4..ffed40d4 100644 --- a/test/helper/http.dart +++ b/test/helper/http.dart @@ -25,8 +25,8 @@ class MockHTTPClient extends http.BaseClient { } class MockResponse extends http.Response { - MockResponse(String body, Map headers, int statusCode) - : super(body, statusCode, headers: headers); + MockResponse(super.body, Map headers, super.statusCode) + : super(headers: headers); factory MockResponse.fromAsset(String name) { final responseData = diff --git a/test/scenarios_test.dart b/test/scenarios_test.dart index c39ceb53..fb453845 100644 --- a/test/scenarios_test.dart +++ b/test/scenarios_test.dart @@ -2,6 +2,7 @@ @Tags(['scenarios']) @TestOn('vm') +library; import 'dart:convert'; @@ -28,8 +29,8 @@ Future createGithubWithScenario(String scenario, var j = json.decode(resp.body); return GitHub( endpoint: j['url'], - auth: - Authentication.withToken('0000000000000000000000000000000000000001')); + auth: const Authentication.withToken( + '0000000000000000000000000000000000000001')); } /// Run scenario tests against ockokits fixtures-server diff --git a/test/server/hooks_test.dart b/test/server/hooks_test.dart index 09deae27..d00cb3de 100644 --- a/test/server/hooks_test.dart +++ b/test/server/hooks_test.dart @@ -57,4 +57,29 @@ void main() { expect(sender.htmlUrl, "https://github.com/Codertocat"); }); }); + + group('EditedPullRequest', () { + test('deserialize with body edit', () { + final pullRequestEditedEvent = PullRequestEvent.fromJson( + jsonDecode(prBodyEditedEvent) as Map); + final changes = pullRequestEditedEvent.changes; + expect(changes, isNotNull); + expect(changes!.body!.from, isNotNull); + assert(changes.body!.from == + '**This should not land until https://github.com/flutter/buildroot/pull/790'); + }); + + test('deserialize with base edit', () { + final pullRequestEditedEvent = PullRequestEvent.fromJson( + jsonDecode(prBaseEditedEvent) as Map); + final changes = pullRequestEditedEvent.changes; + expect(changes, isNotNull); + expect(changes!.body, isNull); + expect(changes.base, isNotNull); + expect(changes.base!.ref, isNotNull); + assert(changes.base!.ref!.from == 'main'); + assert(changes.base!.sha!.from == + 'b3af5d64d3e6e2110b07d71909fc432537339659'); + }); + }); } diff --git a/test/server/hooks_test_data.dart b/test/server/hooks_test_data.dart index c6f12f84..ad02cc10 100644 --- a/test/server/hooks_test_data.dart +++ b/test/server/hooks_test_data.dart @@ -1,4 +1,5 @@ /// Json messages as dart string used for checks model tests. +library; String checkSuiteString = checkSuiteTemplate('requested'); @@ -672,3 +673,1146 @@ const String createString = ''' } } '''; + +const String prBodyEditedEvent = ''' +{ + "action": "edited", + "number": 47609, + "pull_request": { + "url": "https://api.github.com/repos/flutter/engine/pulls/47609", + "id": 1584723957, + "node_id": "PR_kwDOAlZRSc5edPf1", + "html_url": "https://github.com/flutter/engine/pull/47609", + "diff_url": "https://github.com/flutter/engine/pull/47609.diff", + "patch_url": "https://github.com/flutter/engine/pull/47609.patch", + "issue_url": "https://api.github.com/repos/flutter/engine/issues/47609", + "number": 47609, + "state": "open", + "locked": false, + "title": "Upgrade Android SDK to 34 UpsideDownCake", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "body": "~**This should not land until https://github.com/flutter/buildroot/pull/790 (re)lands, and I swap the buildroot url back to the latest commit.**~ Reland of PR to update buildroot at https://github.com/flutter/buildroot/pull/792. Upgrades to android api 34 Also: 1. Upgrades to java 17 in DEPS/ci", + "created_at": "2023-11-02T17:09:59Z", + "updated_at": "2023-11-08T21:00:47Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "8e5e3a59a5cba4239c542ed9a914899a246640b7", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 246348935, + "node_id": "MDU6TGFiZWwyNDYzNDg5MzU=", + "url": "https://api.github.com/repos/flutter/engine/labels/platform-android", + "name": "platform-android", + "color": "A4C639", + "default": false, + "description": null + } + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/flutter/engine/pulls/47609/commits", + "review_comments_url": "https://api.github.com/repos/flutter/engine/pulls/47609/comments", + "review_comment_url": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/flutter/engine/issues/47609/comments", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "head": { + "label": "gmackall:upgrade_to_android14", + "ref": "upgrade_to_android14", + "sha": "a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 547558963, + "node_id": "R_kgDOIKMWMw", + "name": "engine", + "full_name": "gmackall/engine", + "private": false, + "owner": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/gmackall/engine", + "description": "The Flutter engine", + "fork": true, + "url": "https://api.github.com/repos/gmackall/engine", + "forks_url": "https://api.github.com/repos/gmackall/engine/forks", + "keys_url": "https://api.github.com/repos/gmackall/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/gmackall/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/gmackall/engine/teams", + "hooks_url": "https://api.github.com/repos/gmackall/engine/hooks", + "issue_events_url": "https://api.github.com/repos/gmackall/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/gmackall/engine/events", + "assignees_url": "https://api.github.com/repos/gmackall/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/gmackall/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/gmackall/engine/tags", + "blobs_url": "https://api.github.com/repos/gmackall/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/gmackall/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/gmackall/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/gmackall/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/gmackall/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/gmackall/engine/languages", + "stargazers_url": "https://api.github.com/repos/gmackall/engine/stargazers", + "contributors_url": "https://api.github.com/repos/gmackall/engine/contributors", + "subscribers_url": "https://api.github.com/repos/gmackall/engine/subscribers", + "subscription_url": "https://api.github.com/repos/gmackall/engine/subscription", + "commits_url": "https://api.github.com/repos/gmackall/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/gmackall/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/gmackall/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/gmackall/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/gmackall/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/gmackall/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/gmackall/engine/merges", + "archive_url": "https://api.github.com/repos/gmackall/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/gmackall/engine/downloads", + "issues_url": "https://api.github.com/repos/gmackall/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/gmackall/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/gmackall/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/gmackall/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/gmackall/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/gmackall/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/gmackall/engine/deployments", + "created_at": "2022-10-07T22:25:57Z", + "updated_at": "2023-02-02T18:38:07Z", + "pushed_at": "2023-11-08T20:57:02Z", + "git_url": "git://github.com/gmackall/engine.git", + "ssh_url": "git@github.com:gmackall/engine.git", + "clone_url": "https://github.com/gmackall/engine.git", + "svn_url": "https://github.com/gmackall/engine", + "homepage": "https://flutter.dev", + "size": 466778, + "stargazers_count": 0, + "watchers_count": 0, + "language": "C++", + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "flutter:main", + "ref": "main", + "sha": "941e246d4851f652cf13312180174ebc9395fac4", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609" + }, + "html": { + "href": "https://github.com/flutter/engine/pull/47609" + }, + "issue": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609" + }, + "comments": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 4, + "review_comments": 4, + "maintainer_can_modify": true, + "commits": 32, + "additions": 83, + "deletions": 77, + "changed_files": 18 + }, + "changes": { + "body": { + "from": "**This should not land until https://github.com/flutter/buildroot/pull/790" + } + }, + "repository": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "url": "https://api.github.com/orgs/flutter", + "repos_url": "https://api.github.com/orgs/flutter/repos", + "events_url": "https://api.github.com/orgs/flutter/events", + "hooks_url": "https://api.github.com/orgs/flutter/hooks", + "issues_url": "https://api.github.com/orgs/flutter/issues", + "members_url": "https://api.github.com/orgs/flutter/members{/member}", + "public_members_url": "https://api.github.com/orgs/flutter/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "description": "Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase." + }, + "enterprise": { + "id": 1732, + "slug": "alphabet", + "name": "Alphabet", + "node_id": "MDEwOkVudGVycHJpc2UxNzMy", + "avatar_url": "https://avatars.githubusercontent.com/b/1732?v=4", + "description": "", + "website_url": "https://abc.xyz/", + "html_url": "https://github.com/enterprises/alphabet", + "created_at": "2019-12-19T00:30:52Z", + "updated_at": "2023-01-20T00:41:48Z" + }, + "sender": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 10381585, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTAzODE1ODU=" + } +} +'''; + +const String prBaseEditedEvent = ''' +{ + "action": "edited", + "number": 47609, + "pull_request": { + "url": "https://api.github.com/repos/flutter/engine/pulls/47609", + "id": 1584723957, + "node_id": "PR_kwDOAlZRSc5edPf1", + "html_url": "https://github.com/flutter/engine/pull/47609", + "diff_url": "https://github.com/flutter/engine/pull/47609.diff", + "patch_url": "https://github.com/flutter/engine/pull/47609.patch", + "issue_url": "https://api.github.com/repos/flutter/engine/issues/47609", + "number": 47609, + "state": "open", + "locked": false, + "title": "Upgrade Android SDK to 34 UpsideDownCake", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "body": "~**This should not land until https://github.com/flutter/buildroot/pull/790 (re)lands, and I swap the buildroot url back to the latest commit.**~ Reland of PR to update buildroot at https://github.com/flutter/buildroot/pull/792. Upgrades to android api 34 Also: 1. Upgrades to java 17 in DEPS/ci", + "created_at": "2023-11-02T17:09:59Z", + "updated_at": "2023-11-08T21:00:47Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "8e5e3a59a5cba4239c542ed9a914899a246640b7", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 246348935, + "node_id": "MDU6TGFiZWwyNDYzNDg5MzU=", + "url": "https://api.github.com/repos/flutter/engine/labels/platform-android", + "name": "platform-android", + "color": "A4C639", + "default": false, + "description": null + } + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/flutter/engine/pulls/47609/commits", + "review_comments_url": "https://api.github.com/repos/flutter/engine/pulls/47609/comments", + "review_comment_url": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/flutter/engine/issues/47609/comments", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "head": { + "label": "gmackall:upgrade_to_android14", + "ref": "upgrade_to_android14", + "sha": "a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 547558963, + "node_id": "R_kgDOIKMWMw", + "name": "engine", + "full_name": "gmackall/engine", + "private": false, + "owner": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/gmackall/engine", + "description": "The Flutter engine", + "fork": true, + "url": "https://api.github.com/repos/gmackall/engine", + "forks_url": "https://api.github.com/repos/gmackall/engine/forks", + "keys_url": "https://api.github.com/repos/gmackall/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/gmackall/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/gmackall/engine/teams", + "hooks_url": "https://api.github.com/repos/gmackall/engine/hooks", + "issue_events_url": "https://api.github.com/repos/gmackall/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/gmackall/engine/events", + "assignees_url": "https://api.github.com/repos/gmackall/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/gmackall/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/gmackall/engine/tags", + "blobs_url": "https://api.github.com/repos/gmackall/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/gmackall/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/gmackall/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/gmackall/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/gmackall/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/gmackall/engine/languages", + "stargazers_url": "https://api.github.com/repos/gmackall/engine/stargazers", + "contributors_url": "https://api.github.com/repos/gmackall/engine/contributors", + "subscribers_url": "https://api.github.com/repos/gmackall/engine/subscribers", + "subscription_url": "https://api.github.com/repos/gmackall/engine/subscription", + "commits_url": "https://api.github.com/repos/gmackall/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/gmackall/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/gmackall/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/gmackall/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/gmackall/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/gmackall/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/gmackall/engine/merges", + "archive_url": "https://api.github.com/repos/gmackall/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/gmackall/engine/downloads", + "issues_url": "https://api.github.com/repos/gmackall/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/gmackall/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/gmackall/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/gmackall/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/gmackall/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/gmackall/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/gmackall/engine/deployments", + "created_at": "2022-10-07T22:25:57Z", + "updated_at": "2023-02-02T18:38:07Z", + "pushed_at": "2023-11-08T20:57:02Z", + "git_url": "git://github.com/gmackall/engine.git", + "ssh_url": "git@github.com:gmackall/engine.git", + "clone_url": "https://github.com/gmackall/engine.git", + "svn_url": "https://github.com/gmackall/engine", + "homepage": "https://flutter.dev", + "size": 466778, + "stargazers_count": 0, + "watchers_count": 0, + "language": "C++", + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "flutter:main", + "ref": "main", + "sha": "941e246d4851f652cf13312180174ebc9395fac4", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609" + }, + "html": { + "href": "https://github.com/flutter/engine/pull/47609" + }, + "issue": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609" + }, + "comments": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 4, + "review_comments": 4, + "maintainer_can_modify": true, + "commits": 32, + "additions": 83, + "deletions": 77, + "changed_files": 18 + }, + "changes": { + "base": { + "ref": { + "from": "main" + }, + "sha": { + "from": "b3af5d64d3e6e2110b07d71909fc432537339659" + } + } + }, + "repository": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "url": "https://api.github.com/orgs/flutter", + "repos_url": "https://api.github.com/orgs/flutter/repos", + "events_url": "https://api.github.com/orgs/flutter/events", + "hooks_url": "https://api.github.com/orgs/flutter/hooks", + "issues_url": "https://api.github.com/orgs/flutter/issues", + "members_url": "https://api.github.com/orgs/flutter/members{/member}", + "public_members_url": "https://api.github.com/orgs/flutter/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "description": "Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase." + }, + "enterprise": { + "id": 1732, + "slug": "alphabet", + "name": "Alphabet", + "node_id": "MDEwOkVudGVycHJpc2UxNzMy", + "avatar_url": "https://avatars.githubusercontent.com/b/1732?v=4", + "description": "", + "website_url": "https://abc.xyz/", + "html_url": "https://github.com/enterprises/alphabet", + "created_at": "2019-12-19T00:30:52Z", + "updated_at": "2023-01-20T00:41:48Z" + }, + "sender": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 10381585, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTAzODE1ODU=" + } +} +'''; diff --git a/test/unit/checks_test.dart b/test/unit/checks_test.dart index 8af59043..2eb7e6c4 100644 --- a/test/unit/checks_test.dart +++ b/test/unit/checks_test.dart @@ -100,6 +100,9 @@ const checkRunJson = '''{ const String expectedToString = '{"name":"mighty_readme","id":4,"external_id":"","status":"completed","head_sha":"","check_suite":{"id":5},"details_url":"https://example.com","started_at":"2018-05-04T01:14:52.000Z","conclusion":"neutral"}'; +const String newCheckRun = + '{"name":"New CheckRun","id":12345,"external_id":"","status":"queued","head_sha":"","check_suite":{"id":123456},"details_url":"https://example.com","started_at":"2024-12-05T01:05:24.000Z","conclusion":"null"}'; + void main() { group('Check run', () { test('CheckRun fromJson', () { @@ -110,6 +113,14 @@ void main() { expect(checkRun.conclusion, CheckRunConclusion.neutral); }); + test('CheckRun from freshly created and encoded', () { + final checkRun = CheckRun.fromJson(jsonDecode(newCheckRun)); + + expect(checkRun.id, 12345); + expect(checkRun.name, 'New CheckRun'); + expect(checkRun.conclusion, CheckRunConclusion.empty); + }); + test('CheckRun fromJson for skipped conclusion', () { /// The checkRun Json is the official Github values /// diff --git a/test/unit/common/model/pulls_test.dart b/test/unit/common/model/pulls_test.dart new file mode 100644 index 00000000..f4d5fe65 --- /dev/null +++ b/test/unit/common/model/pulls_test.dart @@ -0,0 +1,246 @@ +import 'dart:convert'; + +import 'package:github/src/common/model/pulls.dart'; +import 'package:test/test.dart'; + +const String samplePullRequest = ''' + { + "url": "https://api.github.com/repos/flutter/cocoon/pulls/2703", + "id": 1344460863, + "node_id": "PR_kwDOA8VHis5QItg_", + "html_url": "https://github.com/flutter/cocoon/pull/2703", + "diff_url": "https://github.com/flutter/cocoon/pull/2703.diff", + "patch_url": "https://github.com/flutter/cocoon/pull/2703.patch", + "issue_url": "https://api.github.com/repos/flutter/cocoon/issues/2703", + "number": 2703, + "state": "open", + "locked": false, + "title": "Bump url_launcher from 6.1.10 to 6.1.11 in /dashboard", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "type": "Bot", + "site_admin": false + }, + "body": "Bumps [url_launcher](https://github.com/flutter/packages/tree/main/packages/url_launcher) from 6.1.10 to 6.1.11.", + "created_at": "2023-05-09T22:23:34Z", + "updated_at": "2023-05-09T22:23:35Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "252a1a4370e30631b090eeeda182879985cc8f08", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 3960015931, + "node_id": "LA_kwDOA8VHis7sCQw7", + "url": "https://api.github.com/repos/flutter/cocoon/labels/autosubmit", + "name": "autosubmit", + "color": "0E8A16", + "default": false, + "description": "Merge PR when tree becomes green via auto submit App" + } + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/flutter/cocoon/pulls/2703/commits", + "review_comments_url": "https://api.github.com/repos/flutter/cocoon/pulls/2703/comments", + "review_comment_url": "https://api.github.com/repos/flutter/cocoon/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/flutter/cocoon/issues/2703/comments", + "statuses_url": "https://api.github.com/repos/flutter/cocoon/statuses/57ec5a040c8a631e39b3f3dee82a77fdf79b6e19", + "head": { + "label": "flutter:dependabot/pub/dashboard/url_launcher-6.1.11", + "ref": "dependabot/pub/dashboard/url_launcher-6.1.11", + "sha": "57ec5a040c8a631e39b3f3dee82a77fdf79b6e19", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 63260554, + "node_id": "MDEwOlJlcG9zaXRvcnk2MzI2MDU1NA==", + "name": "cocoon", + "full_name": "flutter/cocoon", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/cocoon", + "description": "Flutter's build coordinator and aggregator", + "fork": false, + "url": "https://api.github.com/repos/flutter/cocoon", + "forks_url": "https://api.github.com/repos/flutter/cocoon/forks", + "created_at": "2016-07-13T16:04:04Z", + "updated_at": "2023-04-12T16:34:46Z", + "pushed_at": "2023-05-09T22:23:35Z", + "git_url": "git://github.com/flutter/cocoon.git", + "ssh_url": "git@github.com:flutter/cocoon.git", + "clone_url": "https://github.com/flutter/cocoon.git", + "svn_url": "https://github.com/flutter/cocoon", + "homepage": null, + "size": 13247, + "stargazers_count": 171, + "watchers_count": 171, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 91, + "open_issues": 2, + "watchers": 171, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "flutter:main", + "ref": "main", + "sha": "152dd99368b8417b2ede8ed49d5923e594a3b0f2", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 63260554, + "node_id": "MDEwOlJlcG9zaXRvcnk2MzI2MDU1NA==", + "name": "cocoon", + "full_name": "flutter/cocoon", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/cocoon", + "description": "Flutter's build coordinator and aggregator", + "fork": false, + "url": "https://api.github.com/repos/flutter/cocoon", + "forks_url": "https://api.github.com/repos/flutter/cocoon/forks", + "created_at": "2016-07-13T16:04:04Z", + "updated_at": "2023-04-12T16:34:46Z", + "pushed_at": "2023-05-09T22:23:35Z", + "git_url": "git://github.com/flutter/cocoon.git", + "ssh_url": "git@github.com:flutter/cocoon.git", + "clone_url": "https://github.com/flutter/cocoon.git", + "svn_url": "https://github.com/flutter/cocoon", + "homepage": null, + "size": 13247, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 91, + "open_issues": 2, + "watchers": 171, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "author_association": "CONTRIBUTOR", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "unstable", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 1, + "additions": 119, + "deletions": 202, + "changed_files": 2 + } +'''; + +void main() { + group('Pull Request fromJson', () { + test('Node ID is collected', () { + final pullRequest = PullRequest.fromJson(jsonDecode(samplePullRequest)); + expect(pullRequest, isNotNull); + expect(pullRequest.nodeId, "PR_kwDOA8VHis5QItg_"); + }); + }); +} diff --git a/test/unit/orgs_service_test.dart b/test/unit/orgs_service_test.dart new file mode 100644 index 00000000..9ba4ff3f --- /dev/null +++ b/test/unit/orgs_service_test.dart @@ -0,0 +1,174 @@ +import 'dart:io'; + +import 'package:github/github.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +void main() { + const teamResponse = ''' + { + "name": "flutter-hackers", + "id": 1753404, + "slug": "flutter-hackers", + "permission": "pull", + "members_count": 255, + "repos_count": 34, + "organization": { + "login": "flutter", + "id": 14101776, + "url": "https://api.github.com/orgs/flutter", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "name": "Flutter", + "company": null, + "blog": "https://flutter.dev", + "location": null, + "email": null, + "public_repos": 30, + "public_gists": 0, + "followers": 6642, + "following": 0, + "html_url": "https://github.com/flutter", + "created_at": "2015-09-03T00:37:37Z", + "updated_at": "2022-03-17T17:35:40Z" + } + } +'''; + + const teamNotFoundResponse = ''' + { + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest/reference/teams#list-teams" + } + '''; + + const activeMemberResponse = ''' + { + "state": "active", + "role": "member", + "url": "https://api.github.com/organizations/14101776/team/1753404/memberships/ricardoamador" + } + '''; + + const pendingMemberResponse = ''' + { + "state": "pending", + "role": "member", + "url": "https://api.github.com/organizations/14101776/team/1753404/memberships/ricardoamador" + } + '''; + + group(GitHub, () { + test('getTeamByName is successful', () async { + Request? request; + + final client = MockClient((r) async { + request = r; + return Response(teamResponse, HttpStatus.ok); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + final team = await organizationsService.getTeamByName( + 'flutter', 'flutter-hackers'); + expect(team.name, 'flutter-hackers'); + expect(team.id, 1753404); + expect(team.organization!.login, 'flutter'); + expect(request, isNotNull); + }); + + test('getTeamByName not found', () async { + Request? request; + + final headers = {}; + headers['content-type'] = 'application/json'; + + final client = MockClient((r) async { + request = r; + return Response(teamNotFoundResponse, HttpStatus.notFound, + headers: headers); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + expect( + () async => organizationsService.getTeamByName( + 'flutter', 'flutter-programmers'), + throwsException); + expect(request, isNull); + }); + + test('getTeamMembership using string name, active', () async { + Request? request; + + final client = MockClient((r) async { + request = r; + return Response(activeMemberResponse, HttpStatus.ok); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + final teamMembershipState = + await organizationsService.getTeamMembershipByName( + 'flutter', + 'flutter-hackers', + 'ricardoamador', + ); + expect(teamMembershipState.isActive, isTrue); + expect(request, isNotNull); + }); + + test('getTeamMembership using string name, pending', () async { + Request? request; + + final client = MockClient((r) async { + request = r; + return Response(pendingMemberResponse, HttpStatus.ok); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + final teamMembershipState = + await organizationsService.getTeamMembershipByName( + 'flutter', + 'flutter-hackers', + 'ricardoamador', + ); + expect(teamMembershipState.isActive, isFalse); + expect(teamMembershipState.isPending, isTrue); + expect(request, isNotNull); + }); + + test('getTeamMembership using string name, null', () async { + Request? request; + + final headers = {}; + headers['content-type'] = 'application/json'; + + final client = MockClient((r) async { + request = r; + return Response( + teamNotFoundResponse, + HttpStatus.notFound, + headers: headers, + ); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + expect( + () async => organizationsService.getTeamMembershipByName( + 'flutter', + 'flutter-hackers', + 'garfield', + ), + throwsException); + expect(request, isNull); + }); + }); +} diff --git a/tool/process_github_schema.dart b/tool/process_github_schema.dart new file mode 100644 index 00000000..14994289 --- /dev/null +++ b/tool/process_github_schema.dart @@ -0,0 +1,621 @@ +import 'dart:convert'; +import 'dart:io'; + +const int width = 72; + +List wordWrap(String body) { + var result = []; + var start = 0; + for (var index = 0; index < body.length; index += 1) { + if ((index == body.length - 1) || + (body[index] == '\n') || + ((body[index] == ' ') && (index - start > width))) { + result.add(body.substring(start, index + 1).trimRight()); + start = index + 1; + } + } + assert(start == body.length); + return result; +} + +typedef GenTypeVisitor = void Function(GenType type); + +abstract class GenType implements Comparable { + GenType(); + + String get name; + String get comment => ''; + + String get signature; + + void cleanup() {} + + String generateDeclaration(); + + void visit(GenTypeVisitor visitor) { + visitor(this); + } + + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(signature == other.signature, + 'cannot merge types with different signatures'); + throw StateError( + 'not sure how to merge $runtimeType with ${other.runtimeType}'); + } + + @override + int compareTo(GenType other) { + return signature.compareTo(other.signature); + } + + @override + String toString() => '$runtimeType($name)'; +} + +class GenPrimitive extends GenType { + GenPrimitive(this.type, this.comment); + + @override + String get name => type.toString(); + + @override + String get signature => name; + + @override + String generateDeclaration() => ''; + + @override + final String comment; + + final Type type; + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenPrimitive) { + assert(type == other.type); + if (comment != other.comment) { + return GenPrimitive( + type, + '$comment\n\n${other.comment}', + ); + } + return this; + } + return super.mergeWith(other, superclass); + } +} + +class GenUnion extends GenType { + GenUnion(this.subtypes); + + @override + String get name => 'Object'; + + @override + String get comment { + var result = StringBuffer(); + result.writeln('One of the following:'); + for (final subtype in subtypes) { + if (subtype.comment.isNotEmpty) { + result.writeln( + ' * [${subtype.name}]: ${subtype.comment.split('\n').first}'); + } else { + result.writeln(' * [${subtype.name}]'); + } + } + return result.toString(); + } + + @override + String get signature { + var subsignatures = + subtypes.map((GenType type) => type.signature).toList() + ..sort() + ..join(','); + return 'Union<$subsignatures>'; + } + + final List subtypes; + + @override + String generateDeclaration() => ''; + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + for (final subtype in subtypes) { + subtype.visit(visitor); + } + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenUnion) { + assert(subtypes.length == other.subtypes.length); + var subtypesA = subtypes..sort(); + var subtypesB = other.subtypes..sort(); + var subtypesC = []; + for (var index = 0; index < subtypesA.length; index += 1) { + subtypesC.add(subtypesA[index].mergeWith(subtypesB[index], null)); + } + return GenUnion(subtypesC); + } + return super.mergeWith(other, superclass); + } +} + +class GenList extends GenType { + GenList(this.members, this.comment); + + @override + String get name => 'List<${members.name}>'; + + @override + final String comment; + + final GenType members; + + @override + String get signature { + return 'List<${members.signature}>'; + } + + @override + String generateDeclaration() => ''; + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + members.visit(visitor); + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenList) { + var newComment = + comment != other.comment ? '$comment\n\n${other.comment}' : comment; + var newMembers = members.mergeWith(other.members, null); + return GenList(newMembers, newComment); + } + return super.mergeWith(other, superclass); + } +} + +class GenAbstractClass extends GenType { + GenAbstractClass(this.name, this.comment, {Map? properties}) + : properties = properties ?? {}; + + @override + final String name; + + @override + final String comment; + + final List subclasses = []; + final Map properties; + + @override + String get signature { + var propertySignatures = properties.keys + .map((String propertyName) => + '$propertyName:${properties[propertyName]!.signature}') + .toList() + ..sort() + ..join(','); + return 'abstract class $name { $propertySignatures }'; + } + + @override + void cleanup() { + if (subclasses.length > 1) { + var names = subclasses.first.properties.keys.toSet(); + properties: + for (final name in names) { + var signature = subclasses.first.properties[name]!.signature; + for (final subclass in subclasses.skip(1)) { + if (!subclass.properties.containsKey(name) || + subclass.properties[name]!.signature != signature) { + continue properties; + } + } + var property = subclasses.first.properties[name]!; + for (final subclass in subclasses.skip(1)) { + property = property.mergeWith(subclass.properties[name]!, null); + } + properties[name] = property; + for (final subclass in subclasses) { + subclass.properties.remove(name); + } + } + } + } + + @override + String generateDeclaration() { + var output = StringBuffer(); + if (comment.isNotEmpty) { + for (final line in wordWrap(comment)) { + output.writeln('/// $line'); + } + } + output.writeln('@JsonSerializable()'); + output.writeln('abstract class $name {'); + output.write(' $name('); + if (properties.isNotEmpty) { + output.writeln('{'); + for (final propertyName in properties.keys.toList()..sort()) { + output.writeln(' this.$propertyName,'); + } + output.write(' }'); + } + output.writeln(');'); + output.writeln(''); + var lastLineWasBlank = true; + for (final propertyName in properties.keys.toList()..sort()) { + if (properties[propertyName]!.comment.isNotEmpty) { + if (!lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + for (final line in wordWrap(properties[propertyName]!.comment)) { + output.writeln(' /// $line'); + } + } else { + lastLineWasBlank = false; + } + output.writeln(' ${properties[propertyName]!.name}? $propertyName;'); + if (lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + } + output.writeln('}'); + return output.toString(); + } + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + for (final subclass in subclasses) { + subclass.visit(visitor); + } + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenAbstractClass) { + assert(name == other.name); + assert(properties.length == other.properties.length); + var newComment = + comment != other.comment ? '$comment\n\n${other.comment}' : comment; + var newProperties = {}; + for (final propertyName in properties.keys) { + newProperties[propertyName] = properties[propertyName]! + .mergeWith(other.properties[propertyName]!, null); + } + var result = + GenAbstractClass(name, newComment, properties: newProperties); + var subclassesA = subclasses..sort(); + var subclassesB = other.subclasses..sort(); + for (var index = 0; index < subclassesA.length; index += 1) { + subclassesA[index].mergeWith(subclassesB[index], result); + } + assert(result.subclasses.length == subclasses.length); + assert(result.subclasses.length == other.subclasses.length); + return result; + } + return super.mergeWith(other, superclass); + } +} + +class GenClass extends GenType { + GenClass(this.name, this.comment, this.superclass, this.properties) { + if (superclass != null) { + superclass!.subclasses.add(this); + } + } + + @override + final String name; + + @override + final String comment; + + final GenAbstractClass? superclass; + final Map properties; + + @override + String get signature { + var propertySignatures = properties.keys + .map((String propertyName) => + '$propertyName:${properties[propertyName]!.signature}') + .toList() + ..sort() + ..join(','); + return 'class $name extends { ${superclass?.signature} } with { $propertySignatures }'; + } + + @override + String generateDeclaration() { + var output = StringBuffer(); + if (comment.isNotEmpty) { + for (final line in wordWrap(comment)) { + output.writeln('/// $line'); + } + } + output.writeln('@JsonSerializable()'); + output.write('class $name '); + if (superclass != null) { + output.write('extends ${superclass!.name} '); + } + output.writeln('{'); + output.writeln(' $name({'); + if (superclass != null) { + for (final propertyName in superclass!.properties.keys.toList()..sort()) { + output.writeln(' super.$propertyName,'); + } + } + for (final propertyName in properties.keys.toList()..sort()) { + output.writeln(' this.$propertyName,'); + } + output.writeln(' });'); + output.writeln(''); + var lastLineWasBlank = true; + for (final propertyName in properties.keys.toList()..sort()) { + if (properties[propertyName]!.comment.isNotEmpty) { + if (!lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + for (final line in wordWrap(properties[propertyName]!.comment)) { + output.writeln(' /// $line'); + } + } else { + lastLineWasBlank = false; + } + output.writeln(' ${properties[propertyName]!.name}? $propertyName;'); + if (lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + } + if (!lastLineWasBlank) { + output.writeln(''); + } + output + .writeln(' Map toJson() => _\$${name}ToJson(this);'); + output.writeln(''); + output.writeln(' factory $name.fromJson(Map input) =>'); + output.writeln(' _\$${name}FromJson(input);'); + output.writeln('}'); + return output.toString(); + } + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + for (final property in properties.values) { + property.visit(visitor); + } + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert((superclass == null) == (this.superclass == null)); + if (other is GenClass) { + assert((other.superclass == null) == (this.superclass == null)); + assert(name == other.name); + assert(properties.length == other.properties.length); + var newComment = + comment != other.comment ? '$comment\n\n${other.comment}' : comment; + var newProperties = {}; + for (final propertyName in properties.keys) { + newProperties[propertyName] = properties[propertyName]! + .mergeWith(other.properties[propertyName]!, null); + } + return GenClass(name, newComment, superclass, newProperties); + } + return super.mergeWith(other, superclass); + } +} + +void assure(bool condition, String Function() callback) { + if (!condition) { + print(callback()); + exit(1); + } +} + +String? camelCase(String? text, {bool uppercase = false}) { + if (text == null) { + return null; + } + var bits = text.split(RegExp('[- _]')); + var result = StringBuffer(); + for (final bit in bits) { + if (bit.isNotEmpty) { + if (result.isNotEmpty || uppercase) { + result.write(String.fromCharCode(bit.runes.first).toUpperCase()); + result.write(String.fromCharCodes(bit.runes.skip(1))); + } else { + result.write(bit); + } + } + } + return result.toString(); +} + +String buildComment(Map schema) { + var description = StringBuffer(); + if (schema['title'] != null) { + description.writeln(schema['title']); + } + if (schema['description'] != null && + schema['description'] != schema['title']) { + if (description.isNotEmpty) { + description.writeln(''); + } + description.writeln(schema['description']); + } + if (schema['format'] != null) { + if (description.isNotEmpty) { + description.writeln(''); + } + description.write('Format: '); + description.writeln(schema['format']); + } + if (schema['examples'] != null) { + assure(schema['examples'] is List, + () => 'examples should be a list, not as in $schema'); + for (final example in schema['examples'] as List) { + if (description.isNotEmpty) { + description.writeln(''); + } + description.writeln('Example: `$example`'); + } + } + return description.toString().trimRight(); +} + +GenType process(Map schema, {String? defaultName}) { + final comment = buildComment(schema); + String type; + if (schema['type'] is List) { + var types = schema['type'] as List; + if (types.length == 2) { + if (types[0] == 'null' && types[1] is String) { + type = types[1] as String; + } else if (types[1] == 'null' && types[0] is String) { + type = types[0] as String; + } else { + print('Arbitrary union types not supported: $types'); + exit(1); + } + } else { + print('Arbitrary union types not supported: $types'); + exit(1); + } + } else if (schema['type'] is String) { + type = schema['type'] as String; + } else { + var anyOf = schema['anyOf'] ?? schema['oneOf']; + if (anyOf != null) { + assure(comment.isEmpty, () => 'lost comment to anyOf/oneOf: $comment'); + assure( + anyOf is List, () => 'anyOf/oneOf key is not a JSON list'); + var subtypes = []; + for (final subtype in anyOf as List) { + assure(subtype is Map, + () => 'type in anyOf/oneOf is not a JSON object'); + subtypes.add(process(subtype as Map)); + } + if (subtypes.length == 2) { + if (subtypes[0] is GenPrimitive && + (subtypes[0] as GenPrimitive).type == Null) { + return subtypes[1]; + } + if (subtypes[1] is GenPrimitive && + (subtypes[1] as GenPrimitive).type == Null) { + return subtypes[0]; + } + } + return GenUnion(subtypes); + } + if (schema['type'] == null) { + print('missing type: $schema'); + exit(1); + } + print('unknown type ${schema['type']}'); + exit(1); + } + if (type == 'array') { + assure(schema['items'] is Map, + () => 'array items are not a JSON object'); + return GenList(process(schema['items'] as Map), comment); + } + if (type == 'object') { + var anyOf = schema['anyOf']; + if (anyOf != null) { + assure(anyOf is List, () => 'anyOf key is not a JSON list'); + var result = GenAbstractClass( + camelCase(schema['title'] as String?) ?? '##unnamed##', + comment, + ); + for (final subschema in anyOf as List) { + assure(subschema is Map, + () => 'anyOf value is not a JSON object'); + var subclass = processObject(subschema as Map, + superclass: result); + assert(result.subclasses.last == subclass); + } + return result; + } + return processObject(schema, defaultName: defaultName); + } + if (type == 'null') { + return GenPrimitive(Null, comment); + } + if (type == 'boolean') { + return GenPrimitive(bool, comment); + } + if (type == 'integer') { + return GenPrimitive(int, comment); + } + if (type == 'string') { + return GenPrimitive(String, comment); + } + print('unknown type $type'); + exit(1); +} + +GenClass processObject(Map schema, + {GenAbstractClass? superclass, String? comment, String? defaultName}) { + assert(schema['anyOf'] == null); + comment ??= buildComment(schema); + var properties = {}; + var propertiesData = schema['properties']; + assure(propertiesData is Map, + () => 'properties key is not a JSON map'); + for (final propertyName in (propertiesData as Map).keys) { + var propertyData = propertiesData[propertyName]; + assure(propertyData is Map, + () => 'property $propertyName is not a JSON object'); + properties[camelCase(propertyName)!] = process( + propertyData as Map, + defaultName: camelCase(propertyName, uppercase: true)); + } + return GenClass( + camelCase(schema['title'] as String?) ?? defaultName ?? '##unnamed##', + comment, + superclass, + properties, + ); +} + +void main(List arguments) { + if (arguments.length != 1) { + print( + 'Command must be run with one argument, the file name of the schema to process.'); + exit(1); + } + Object schema = json.decode(File(arguments.single).readAsStringSync()); + assure(schema is Map, () => 'schema is not a JSON object'); + var rootType = process(schema as Map); + rootType.visit((GenType type) { + type.cleanup(); + }); + var declarations = {}; + rootType.visit((GenType type) { + var declaration = type.generateDeclaration().trimRight(); + declarations.add(declaration); + }); + for (final declaration in declarations) { + print(declaration); + print(''); + } + print('// root type is: ${rootType.name}'); +} diff --git a/tool/release_unreleased_prs.dart b/tool/release_unreleased_prs.dart deleted file mode 100644 index b8cf8b17..00000000 --- a/tool/release_unreleased_prs.dart +++ /dev/null @@ -1,178 +0,0 @@ -import 'dart:io'; -import 'package:github/github.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:yaml_edit/yaml_edit.dart'; - -/////////////////////////////////////////////////////////// -const mainBranchName = 'master'; -const semverMajor = 'semver:major'; -const semverMinor = 'semver:minor'; -const semverPatch = 'semver:patch'; -const semvers = [semverMajor, semverMinor, semverPatch]; -const fullrepo = 'SpinlockLabs/github.dart'; -/////////////////////////////////////////////////////////// - -var _gh = GitHub(auth: findAuthenticationFromEnvironment()); -var _slug = RepositorySlug.full(fullrepo); - -Future main(List args) async { - // get the latest released version - var latestVersion = await getLatestVersion(_slug); - - // get all PRs (issues) that are merged but unreleased - var unreleased = await getUnreleasedPRs(); - - if (unreleased.isEmpty) { - print('No unreleased PRs found'); - return; - } - - // Calculate the next version - var nextVersion = getNextVersion(latestVersion, unreleased); - - // Use the new version to generate release notes - var notes = await generateReleaseNotes(latestVersion.toString(), nextVersion); - - // update the changelog with the new release notes - updateChangeLog(notes, nextVersion); - - // update the version in the pubspec - updatePubspec(nextVersion); - - // commit those changes and push them - commitUpdates(nextVersion); - - // create a new release in github at main - await createRelease(nextVersion, mainBranchName); - - // remove the unreleased labels - for (final i in unreleased) { - await _gh.issues.removeLabelForIssue(_slug, i.number, 'unreleased'); - await _gh.issues.addLabelsToIssue(_slug, i.number, ['released']); - await _gh.issues.createComment(_slug, i.number, - 'Released in version $nextVersion https://github.com/$fullrepo/releases/tag/$nextVersion'); - } - - exit(0); -} - -String run(String cmd, {List? rest}) { - var args = []; - if (rest != null) { - args = rest; - } else { - args = cmd.split(' '); - if (args.isEmpty) { - return ''; - } - cmd = args.removeAt(0); - } - var result = Process.runSync(cmd, args); - if (result.exitCode != 0) { - print('Command failed'); - } - if (result.stdout != null) { - print(result.stdout); - } - if (result.stderr != null) { - print(result.stderr); - } - if (result.exitCode != 0) { - exit(6); - } - - return result.stdout; -} - -Future getLatestVersion(RepositorySlug slug) async { - var latestRelease = await _gh.repositories.getLatestRelease(slug); - var latestTag = latestRelease.tagName!; - print('Latest Tag: $latestTag'); - return Version.parse(latestTag); -} - -Future> getUnreleasedPRs() async { - print('Loading unreleased PRs...'); - var prs = await _gh.search - .issues( - 'repo:${_slug.fullName} is:pull-request label:unreleased -label:no_release_on_merge state:closed', - sort: 'desc') - .toList(); - print('${prs.length} loaded'); - return prs; -} - -String getNextVersion(Version currentVersion, List unreleased) { - var semvers = {}; - for (final pr in unreleased) { - var prlabels = pr.labels - .where((element) => element.name.startsWith('semver:')) - .toList(); - for (final l in prlabels) { - semvers.add(l.name); - } - } - print('Calculating next version based on $semvers'); - var newVersion = ''; - if (semvers.contains('semver:major')) { - newVersion = currentVersion.nextMajor.toString(); - } else if (semvers.contains('semver:minor')) { - newVersion = currentVersion.nextMinor.toString(); - } else if (semvers.contains('semver:patch')) { - newVersion = currentVersion.nextPatch.toString(); - } - print('Next Version: $newVersion'); - return newVersion; -} - -Future generateReleaseNotes( - String fromVersion, String newVersion) async { - var notes = await _gh.repositories.generateReleaseNotes(CreateReleaseNotes( - _slug.owner, _slug.name, newVersion, - previousTagName: fromVersion)); - - var releaseNotes = notes.body.replaceFirst('## What\'s Changed', ''); - - var r = '## $newVersion\n$releaseNotes'; - print(r); - return r; -} - -void updateChangeLog(String notes, String version) { - var log = File('CHANGELOG.md'); - var logdata = log.existsSync() ? log.readAsStringSync() : ''; - if (logdata.contains('## $version')) { - return; - } - log.writeAsStringSync('$notes\n\n$logdata'); -} - -void updatePubspec(String newVersion) { - var f = File('pubspec.yaml'); - var editor = YamlEditor(f.readAsStringSync()); - editor.update(['version'], newVersion); - f.writeAsStringSync(editor.toString()); -} - -Future createRelease(String version, String target) async { - print('Creating release ...'); - var release = await _gh.repositories.createRelease( - _slug, - CreateRelease.from( - tagName: version, - name: version, - generateReleaseNotes: true, - targetCommitish: target, - isDraft: false, - isPrerelease: false)); - - print('Release ${release.name} created ${release.createdAt}'); - print(release.body); - return release; -} - -void commitUpdates(String version) { - run('git add pubspec.yaml CHANGELOG.md'); - run('git', rest: ['commit', '-m', 'prep $version']); - run('git push'); -} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy