message:
Remove services requiring HTML parsing
Not exposed via a regular API - likely fragile

diff:
@@ -6,8 +6,6 @@ import "dart:async";
import "dart:convert"
show base64Decode, base64Encode, jsonEncode, jsonDecode, LineSplitter, utf8;
-import "package:html/dom.dart" as html;
-import "package:html/parser.dart" as html_parser;
import "package:http/http.dart" as http;
import 'package:http_parser/http_parser.dart' as http_parser;
import "package:json_annotation/json_annotation.dart";
@@ -24,7 +22,6 @@ part "common.g.dart";
part "common/activity_service.dart";
part "common/authorizations_service.dart";
part "common/blog_service.dart";
-part "common/explore_service.dart";
part "common/gists_service.dart";
part "common/git_service.dart";
part "common/github.dart";
@@ -33,7 +30,6 @@ part "common/misc_service.dart";
part "common/model/activity.dart";
part "common/model/authorizations.dart";
part "common/model/blog.dart";
-part "common/model/explore.dart";
part "common/model/gists.dart";
part "common/model/git.dart";
part "common/model/issues.dart";

diff:
-part of github.common;
-
-/// The [ExploreService] provides methods for exploring GitHub.
-///
-/// API docs: https://github.com/explore
-class ExploreService extends Service {
- ExploreService(GitHub github) : super(github);
-
- Stream<TrendingRepository> listTrendingRepositories(
- {String language, String since: "daily"}) {
- var url = "https://github.com/trending";
-
- if (language != null) url += "?l=$language";
-
- if (since != null)
- url += language == null ? "?since=$since" : "&since=$since";
-
- var controller = new StreamController<TrendingRepository>();
-
- _github.client.get(url).then((response) {
- var doc = html_parser.parse(response.body);
- var items = doc.querySelectorAll(
- "li.repo-leaderboard-list-item.leaderboard-list-item");
-
- for (var item in items) {
- var repo = new TrendingRepository();
- repo.rank = item.querySelector("a.leaderboard-list-rank").text;
- repo.titleObject =
- item.querySelector("h2.repo-leaderboard-title").querySelector("a");
- var desc = item.querySelector("p.repo-leaderboard-description");
-
- if (desc == null) {
- repo.description = "No Description";
- } else {
- repo.description = desc.text;
- }
-
- controller.add(repo);
- }
-
- controller.close();
- });
-
- return controller.stream;
- }
-
- Future<Showcase> getShowcase(ShowcaseInfo info) {
- var completer = new Completer<Showcase>();
-
- _github.client.get(info.url).then((response) {
- var doc = html_parser.parse(response.body);
- var showcase = new Showcase();
-
- var title = doc.querySelector(".collection-header").text;
- var lastUpdated = parseDateTime(doc
- .querySelector(".meta-info.last-updated")
- .querySelector("time")
- .attributes['datetime']);
- var page = doc.querySelector(".collection-page");
-
- var description = page.querySelector(".collection-description");
-
- // TODO: This is most likely wrong
- showcase.description = description.text;
- showcase.lastUpdated = lastUpdated;
- showcase.title = title;
- showcase.items = [];
-
- var repos = page.querySelectorAll(".collection-repo");
-
- for (var repo in repos) {
- var repoTitle = repo.querySelector(".collection-repo-title");
- var path = repoTitle.querySelector("a").attributes['href'];
- var url = "https://githb.com$path";
- var name = path.substring(1);
-
- var item = new ShowcaseItem();
-
- item.name = name;
-
- item.url = url;
-
- showcase.items.add(item);
- }
-
- completer.complete(showcase);
- });
-
- return completer.future;
- }
-
- Stream<ShowcaseInfo> listShowcases() {
- var controller = new StreamController<ShowcaseInfo>();
-
- Function handleResponse;
-
- handleResponse = (response) {
- var doc = html_parser.parse(response.body);
-
- var cards = doc.querySelectorAll(".collection-card");
-
- for (var card in cards) {
- var title = card.querySelector(".collection-card-title").text;
- var description = card.querySelector(".collection-card-body").text;
- var img = card.querySelector(".collection-card-image");
- var url = "https://github.com" + img.attributes['href'];
-
- var showcase = new ShowcaseInfo();
-
- showcase
- ..title = title
- ..description = description
- ..url = url;
-
- controller.add(showcase);
- }
-
- var pag = doc.querySelector(".pagination");
-
- var links = pag.querySelectorAll("a");
-
- bool didFetchMore = false;
-
- for (var link in links) {
- if (link.text.contains("Next")) {
- didFetchMore = true;
-
- var client = new http.Client();
-
- client.get(link.attributes['href']).then(handleResponse);
- }
- }
-
- if (!didFetchMore) {
- controller.close();
- }
- };
-
- _github.client.get("https://github.com/showcases").then(handleResponse);
-
- return controller.stream;
- }
-}

diff:
@@ -26,7 +26,6 @@ class GitHub {
ActivityService _activity;
AuthorizationsService _authorizations;
BlogService _blog;
- ExploreService _explore;
GistsService _gists;
GitService _git;
IssuesService _issues;
@@ -103,14 +102,6 @@ class GitHub {
return _blog;
}
- /// Service to explore GitHub.
- ExploreService get explore {
- if (_explore == null) {
- _explore = new ExploreService(this);
- }
- return _explore;
- }
-
/// Service for gist related methods of the GitHub API.
GistsService get gists {
if (_gists == null) {

diff:
@@ -65,39 +65,6 @@ class MiscService extends Service {
_github.getJSON("https://status.github.com/api/status.json",
statusCode: StatusCodes.OK, convert: APIStatus.fromJSON);
- /// Returns a stream of Octocats from Octodex.
- ///
- /// See: https://octodex.github.com/
- Stream<Octocat> listOctodex({bool cors: false}) {
- var controller = new StreamController<Octocat>();
-
- var u = "http://feeds.feedburner.com/Octocats.xml";
-
- _github.client
- .get(
- "${cors ? "http://whateverorigin.org/get?url=" : ""}${cors ? Uri.encodeComponent(u) : u}")
- .then((response) {
- var document = html_parser.parse(response.body);
- document.querySelectorAll("entry").forEach((entry) {
- var name = entry.querySelector("title").text;
- var c = "" +
- entry.querySelector("content").innerHtml +
- "";
- var content = html_parser.parse(c);
- var image = content.querySelector("a img").attributes['src'];
- var url = entry.querySelector("link").attributes['href'];
-
- controller.add(new Octocat()
- ..image = image
- ..name = name
- ..url = url);
- });
- return controller.close();
- });
-
- return controller.stream;
- }
-
/// Returns an ASCII Octocat with the specified [text].
Future<String> getOctocat([String text]) {
var params = <String, String>{};

diff:
-part of github.common;
-
-class TrendingRepository {
- String rank;
- html.Element titleObject;
- String get title => titleObject.text;
-
- String get url => "https://github.com/$title";
- String description;
-}
-
-class ShowcaseInfo {
- String title;
- String description;
- String url;
-}
-
-class Showcase extends ShowcaseInfo {
- DateTime lastUpdated;
- List<ShowcaseItem> items;
-}
-
-class ShowcaseItem {
- String name;
- String url;
-}

diff:
@@ -6,7 +6,6 @@ homepage: https://github.com/DirectMyFile/github.dart
environment:
sdk: '>=2.0.0-dev.36 <3.0.0'
dependencies:
- html: '>=0.12.0 <0.14.0'
http: '>=0.11.3 <0.13.0'
http_parser: ^3.1.1
json_annotation: ^1.1.0

diff:
-import "package:github/server.dart";
-
-void main() {
- var github = createGitHubClient();
-
- github.explore.listShowcases().listen((info) {
- print("- ${info.title}");
- }).onDone(() => github.dispose());
-}

diff:
-import "package:github/server.dart";
-
-void main() {
- var github = createGitHubClient();
-
- github.explore
- .listTrendingRepositories(language: "Dart", since: "month")
- .listen((repo) => print("${repo.title}: ${repo.description}"));
-}


message:
Remove services requiring XML parsing
Not exposed via a regular API - likely fragile

change_type: MODIFY

diff:
@@ -9,7 +9,6 @@ import "dart:convert"
import "package:http/http.dart" as http;
import 'package:http_parser/http_parser.dart' as http_parser;
import "package:json_annotation/json_annotation.dart";
-import "package:xml/xml.dart" as xml;
import 'common/model/users.dart';
import "common/util/pagination.dart";
@@ -21,7 +20,6 @@ export "common/util/pagination.dart";
part "common.g.dart";
part "common/activity_service.dart";
part "common/authorizations_service.dart";
-part "common/blog_service.dart";
part "common/gists_service.dart";
part "common/git_service.dart";
part "common/github.dart";
@@ -29,7 +27,6 @@ part "common/issues_service.dart";
part "common/misc_service.dart";
part "common/model/activity.dart";
part "common/model/authorizations.dart";
-part "common/model/blog.dart";
part "common/model/gists.dart";
part "common/model/git.dart";
part "common/model/issues.dart";

change_type: DELETE

diff:
-part of github.common;
-
-/// The [BlogService] provides methods to retrieve blog posts from GitHub.
-class BlogService extends Service {
- BlogService(GitHub github) : super(github);
-
- /// Returns a stream of blog posts for the specified [url].
- Stream<BlogPost> listPosts([String url = "https://github.com/blog.atom"]) {
- var controller = new StreamController<BlogPost>();
- _github.client.get(url).then((response) {
- var document = xml.parse(response.body);
-
- var entries = document.rootElement.findElements("entry");
-
- for (var entry in entries) {
- controller.add(BlogPost.fromXML(entry));
- }
-
- controller.close();
- });
-
- return controller.stream;
- }
-}

diff:
@@ -25,7 +25,6 @@ class GitHub {
ActivityService _activity;
AuthorizationsService _authorizations;
- BlogService _blog;
GistsService _gists;
GitService _git;
IssuesService _issues;
@@ -94,14 +93,6 @@ class GitHub {
return _authorizations;
}
- /// Service to retrieve blog posts.
- BlogService get blog {
- if (_blog == null) {
- _blog = new BlogService(this);
- }
- return _blog;
- }
-
/// Service for gist related methods of the GitHub API.
GistsService get gists {
if (_gists == null) {

diff:
-part of github.common;
-
-/// Model class for a blog post.
-class BlogPost {
- DateTime publishedAt;
- DateTime updatedAt;
-
- String title;
- String url;
- String category;
- String content;
- String author;
-
- static BlogPost fromXML(xml.XmlElement node) {
- var children = node.children;
-
- xml.XmlElement query(String tagName) => children
- .firstWhere((it) => it is xml.XmlElement && it.name.local == tagName);
-
- var title = query("title").text;
- var content = query("content").text;
- var link = query("link").getAttribute("href");
- var category = query("category").text;
- var author = query("author").children[0].text;
-
- var post = new BlogPost();
-
- post.author = author;
- post.title = title;
- post.content = content;
- post.category = category;
- post.url = link;
-
- return post;
- }
-}

diff:
@@ -9,7 +9,6 @@ dependencies:
http: '>=0.11.3 <0.13.0'
http_parser: ^3.1.1
json_annotation: ^1.1.0
- xml: '>=2.0.0 <4.0.0'
dev_dependencies:
build_runner: ^0.10.0
json_serializable: ^1.1.0

diff:
-import "package:github/server.dart";
-
-void main() {
- var github = createGitHubClient();
-
- github.blog.listPosts().listen((post) {
- print(post.title);
- }).onDone(() => github.dispose());
-}

license: MIT License


message: Update to latest json_ packages

diff:
@@ -34,7 +34,7 @@ GitCommit _$GitCommitFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$CreateGitCommitToJson(CreateGitCommit instance) {
- var val = <String, dynamic>{};
+ final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {
@@ -56,7 +56,7 @@ GitCommitUser _$GitCommitUserFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$GitCommitUserToJson(GitCommitUser instance) {
- var val = <String, dynamic>{};
+ final val = <String, dynamic>{};
void writeNotNull(String key, dynamic value) {
if (value != null) {

diff:
@@ -4,13 +4,13 @@ author: Kenneth Endfinger <kaendfinger@gmail.com>
description: GitHub API Client Library
homepage: https://github.com/DirectMyFile/github.dart
environment:
- sdk: '>=2.0.0-dev.36 <3.0.0'
+ sdk: '>=2.0.0 <3.0.0'
dependencies:
http: '>=0.11.3 <0.13.0'
http_parser: ^3.1.1
- json_annotation: ^1.1.0
+ json_annotation: ^2.0.0
dev_dependencies:
build_runner: ^1.0.0
- json_serializable: ^1.1.0
+ json_serializable: ^2.0.0
test: ^1.3.0
mockito: ^3.0.0

original_message: Update to latest json_ packages (#117)


message: Fix RepositoriesService.listContributors

diff:
+## v4.0.2
+
+- Fix return type of `RepositoriesService.listContributors`.
+
## v4.0.1
- Fix cast errors in event and issue queries.

diff:
@@ -152,10 +152,10 @@ class RepositoriesService extends Service {
/// Lists the contributors of the specified repository.
///
/// API docs: https://developer.github.com/v3/repos/#list-contributors
- Stream<Tag> listContributors(RepositorySlug slug, {bool anon: false}) {
+ Stream<User> listContributors(RepositorySlug slug, {bool anon: false}) {
return new PaginationHelper(_github).objects(
'GET', '/repos/${slug.fullName}/contributors', User.fromJson,
- params: {"anon": anon.toString()}) as Stream<Tag>;
+ params: {"anon": anon.toString()});
}
/// Lists the teams of the specified repository.

diff:
name: github
-version: 4.0.1
+version: 4.0.2-dev
author: Kenneth Endfinger <kaendfinger@gmail.com>
description: GitHub API Client Library
homepage: https://github.com/DirectMyFile/github.dart


diff:
## v4.0.2
- Fix return type of `RepositoriesService.listContributors`.
+- Fix return type of `RepositoriesService.createRelease`.
## v4.0.1

diff:
@@ -135,7 +135,7 @@ class RepositoriesService extends Service {
"default_branch": "defaultBranch"
});
return _github.postJSON("/repos/${repo.fullName}",
- body: jsonEncode(data), statusCode: 200) as Future<Repository>;
+ body: jsonEncode(data), statusCode: 200);
}
/// Deletes a repository.
@@ -474,8 +474,7 @@ class RepositoriesService extends Service {
///
/// API docs: https://developer.github.com/v3/repos/keys/#create
Future<PublicKey> createDeployKey(RepositorySlug slug, CreatePublicKey key) {
- return _github.postJSON("/repos/${slug.fullName}/keys", body: key.toJSON())
- as Future<PublicKey>;
+ return _github.postJSON("/repos/${slug.fullName}/keys", body: key.toJSON());
}
// TODO: Implement editDeployKey: https://developer.github.com/v3/repos/keys/#edit
@@ -519,10 +518,9 @@ class RepositoriesService extends Service {
/// Creates a Release based on the specified [release].
///
/// API docs: https://developer.github.com/v3/repos/releases/#create-a-release
- Future<Hook> createRelease(RepositorySlug slug, CreateRelease release) {
+ Future<Release> createRelease(RepositorySlug slug, CreateRelease release) {
return _github.postJSON("/repos/${slug.fullName}/releases",
- convert: Release.fromJson,
- body: jsonEncode(release.toJson())) as Future<Hook>;
+ convert: Release.fromJson, body: jsonEncode(release.toJson()));
}
// TODO: Implement editRelease: https://developer.github.com/v3/repos/releases/#edit-a-release


diff:
- Fix return type of `RepositoriesService.listContributors`.
- Fix return type of `RepositoriesService.createRelease`.
+- Fixed `RepositoriesService.listContributorStats`.
+ - Removed unsupported `limit` parameter.
+ - Removed flaky retry logic. Instead, `NotReady` is thrown, which can be used
+ to decide to retry at the call site.
+ - Made associated classes `ContributorStatistics` and
+ `ContributorWeekStatistics` immutable. Since these classes are only meant as
+ return values, we're not treating this as a breaking change.
## v4.0.1

diff:
@@ -346,3 +346,23 @@ Map<String, dynamic> _$LinksToJson(Links instance) => <String, dynamic>{
'git': instance.git?.toString(),
'html': instance.html?.toString()
};
+
+ContributorStatistics _$ContributorStatisticsFromJson(
+ Map<String, dynamic> json) {
+ return ContributorStatistics(
+ json['author'] == null
+ ? null
+ : User.fromJson(json['author'] as Map<String, dynamic>),
+ json['total'] as int,
+ (json['weeks'] as List)
+ ?.map((e) => e == null
+ ? null
+ : ContributorWeekStatistics.fromJson(e as Map<String, dynamic>))
+ ?.toList());
+}
+
+ContributorWeekStatistics _$ContributorWeekStatisticsFromJson(
+ Map<String, dynamic> json) {
+ return ContributorWeekStatistics(
+ json['w'] as int, json['a'] as int, json['d'] as int, json['c'] as int);
+}

diff:
part of github.common;
/// Model class for a contributor's statistics for a repository.
+@JsonSerializable(createToJson: false)
class ContributorStatistics {
/// The Author
- User author;
+ final User author;
/// Total Commits
- int total;
+ final int total;
/// Weekly Statistics
- List<ContributorWeekStatistics> weeks;
+ final List<ContributorWeekStatistics> weeks;
- static ContributorStatistics fromJSON(Map<String, dynamic> input) {
- if (input == null) return null;
+ ContributorStatistics(this.author, this.total, this.weeks);
- return new ContributorStatistics()
- ..author = User.fromJson(input['author'] as Map<String, dynamic>)
- ..total = input['total']
- ..weeks = (input['weeks'] as List<Map<String, dynamic>>)
- .map((it) => ContributorWeekStatistics.fromJSON(it));
- }
+ factory ContributorStatistics.fromJson(Map<String, dynamic> json) =>
+ _$ContributorStatisticsFromJson(json);
}
/// Model class to represent the number of additions, deletions and commits
/// a contributor made in a given week.
+@JsonSerializable(createToJson: false)
class ContributorWeekStatistics {
/// Beginning of the Week (As a Unix Timestamp)
- String start;
+ @JsonKey(name: 'w')
+ final int start;
/// Number of Additions
- int additions;
+ @JsonKey(name: 'a')
+ final int additions;
/// Number of Deletions
- int deletions;
+ @JsonKey(name: 'd')
+ final int deletions;
/// Number of Commits
- int commits;
+ @JsonKey(name: 'c')
+ final int commits;
- static ContributorWeekStatistics fromJSON(Map<String, dynamic> input) {
- if (input == null) return null;
+ ContributorWeekStatistics(
+ this.start, this.additions, this.deletions, this.commits);
- return new ContributorWeekStatistics()
- ..additions = input['a']
- ..deletions = input['d']
- ..commits = input['c']
- ..start = input['w'];
- }
+ factory ContributorWeekStatistics.fromJson(Map<String, dynamic> json) =>
+ _$ContributorWeekStatisticsFromJson(json);
+
+ String toString() =>
+ 'ContributorWeekStatistics(start: $start, commits: $commits, additions: $additions, deletions: $deletions)';
}
/// Model class for contributor participation.

diff:
@@ -533,29 +533,27 @@ class RepositoriesService extends Service {
/// Lists repository contributor statistics.
///
+ /// It's possible that this API will throw [NotReady] in which case you should
+ /// try the call again later.
+ ///
/// API docs: https://developer.github.com/v3/repos/statistics/#contributors
- Future<List<ContributorStatistics>> listContributorStats(RepositorySlug slug,
- {int limit: 30}) {
- var completer = new Completer<List<ContributorStatistics>>();
+ Future<List<ContributorStatistics>> listContributorStats(
+ RepositorySlug slug,
+ ) async {
var path = "/repos/${slug.fullName}/stats/contributors";
- var handle;
- handle = (json) {
- if (json is Map) {
- new Future.delayed(new Duration(milliseconds: 200), () {
- _github.getJSON(path,
- statusCode: 200,
- convert: handle,
- params: {"per_page": limit.toString()});
- });
+ var response = await _github.request('GET', path,
+ headers: {"Accept": "application/vnd.github.v3+json"});
+
+ if (response.statusCode == 200) {
+ return (jsonDecode(response.body) as List)
+ .map((e) => ContributorStatistics.fromJson(e))
+ .toList();
+ } else if (response.statusCode == 202) {
+ throw NotReady(_github, path);
+ }
+ _github.handleStatusCode(response);
+ // will never get here!
return null;
- } else {
- completer.complete(json.map(
- (Map<String, dynamic> it) => ContributorStatistics.fromJSON(it)));
- }
- };
- _github
- .getJSON(path, convert: handle, params: {"per_page": limit.toString()});
- return completer.future;
}
/// Fetches commit counts for the past year.

diff:
@@ -13,6 +13,11 @@ class GitHubError implements Exception {
String toString() => "GitHub Error: $message";
}
+class NotReady extends GitHubError {
+ NotReady(GitHub github, String path)
+ : super(github, 'Not ready. Try again later', apiUrl: path);
+}
+
/// GitHub Entity was not found
class NotFound extends GitHubError {
NotFound(GitHub github, String msg) : super(github, msg);


message: Use pkg:meta alwaysThrows to flag always throwing functions

diff:
@@ -9,6 +9,7 @@ import "dart:convert"
import "package:http/http.dart" as http;
import 'package:http_parser/http_parser.dart' as http_parser;
import "package:json_annotation/json_annotation.dart";
+import 'package:meta/meta.dart' as meta show alwaysThrows;
import 'common/model/repos_releases.dart';
import 'common/model/users.dart';

diff:
@@ -286,6 +286,7 @@ class GitHub {
///
/// Internal method to handle status codes
///
+ @meta.alwaysThrows
void handleStatusCode(http.Response response) {
String message;
List<Map<String, String>> errors;
@@ -396,7 +397,6 @@ class GitHub {
if (statusCode != null && statusCode != response.statusCode) {
fail != null ? fail(response) : null;
handleStatusCode(response);
- return null;
} else
return response;
}

diff:
@@ -552,8 +552,6 @@ class RepositoriesService extends Service {
throw NotReady(_github, path);
}
_github.handleStatusCode(response);
- // will never get here!
- return null;
}
/// Fetches commit counts for the past year.

diff:
@@ -9,6 +9,7 @@ dependencies:
http: '>=0.11.3 <0.13.0'
http_parser: ^3.1.1
json_annotation: ^2.0.0
+ meta: ^1.1.0
dev_dependencies:
build_runner: any
build_test: any


message: GitHub: remove redundant auth logic

diff:
@@ -205,14 +205,6 @@ class GitHub {
headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json");
- if (auth.isToken) {
- headers.putIfAbsent("Authorization", () => "token ${auth.token}");
- } else if (auth.isBasic) {
- var userAndPass =
- base64Encode(utf8.encode('${auth.username}:${auth.password}'));
- headers.putIfAbsent("Authorization", () => "basic $userAndPass");
- }
-
var response = await request("GET", path,
headers: headers, params: params, statusCode: statusCode, fail: fail);
@@ -266,14 +258,6 @@ class GitHub {
headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json");
- if (auth.isToken) {
- headers.putIfAbsent("Authorization", () => "token ${auth.token}");
- } else if (auth.isBasic) {
- var userAndPass =
- base64Encode(utf8.encode('${auth.username}:${auth.password}'));
- headers.putIfAbsent("Authorization", () => "basic $userAndPass");
- }
-
var response = await request("POST", path,
headers: headers,
params: params,

original_message: GitHub: remove redundant auth logic


message: GitHub: code reorg

diff:
@@ -267,53 +267,6 @@ class GitHub {
return convert(jsonDecode(response.body));
}
- ///
- /// Internal method to handle status codes
- ///
- @meta.alwaysThrows
- void handleStatusCode(http.Response response) {
- String message;
- List<Map<String, String>> errors;
- if (response.headers['content-type'].contains('application/json')) {
- var json = jsonDecode(response.body);
- message = json['message'];
- errors = json['errors'] as List<Map<String, String>>;
- }
- switch (response.statusCode) {
- case 404:
- throw new NotFound(this, "Requested Resource was Not Found");
- break;
- case 401:
- throw new AccessForbidden(this);
- case 400:
- if (message == "Problems parsing JSON") {
- throw new InvalidJSON(this, message);
- } else if (message == "Body should be a JSON Hash") {
- throw new InvalidJSON(this, message);
- } else
- throw new BadRequest(this);
- break;
- case 422:
- var buff = new StringBuffer();
- buff.writeln();
- buff.writeln(" Message: $message");
- if (errors != null) {
- buff.writeln(" Errors:");
- for (Map<String, String> error in errors) {
- var resource = error['resource'];
- var field = error['field'];
- var code = error['code'];
- buff
- ..writeln(" Resource: $resource")
- ..writeln(" Field $field")
- ..write(" Code: $code");
- }
- }
- throw new ValidationFailed(this, buff.toString());
- }
- throw new UnknownError(this, message);
- }
-
/// Handles Authenticated Requests in an easy to understand way.
///
/// [method] is the HTTP method.
@@ -385,6 +338,53 @@ class GitHub {
return response;
}
+ ///
+ /// Internal method to handle status codes
+ ///
+ @meta.alwaysThrows
+ void handleStatusCode(http.Response response) {
+ String message;
+ List<Map<String, String>> errors;
+ if (response.headers['content-type'].contains('application/json')) {
+ var json = jsonDecode(response.body);
+ message = json['message'];
+ errors = json['errors'] as List<Map<String, String>>;
+ }
+ switch (response.statusCode) {
+ case 404:
+ throw new NotFound(this, "Requested Resource was Not Found");
+ break;
+ case 401:
+ throw new AccessForbidden(this);
+ case 400:
+ if (message == "Problems parsing JSON") {
+ throw new InvalidJSON(this, message);
+ } else if (message == "Body should be a JSON Hash") {
+ throw new InvalidJSON(this, message);
+ } else
+ throw new BadRequest(this);
+ break;
+ case 422:
+ var buff = new StringBuffer();
+ buff.writeln();
+ buff.writeln(" Message: $message");
+ if (errors != null) {
+ buff.writeln(" Errors:");
+ for (Map<String, String> error in errors) {
+ var resource = error['resource'];
+ var field = error['field'];
+ var code = error['code'];
+ buff
+ ..writeln(" Resource: $resource")
+ ..writeln(" Field $field")
+ ..write(" Code: $code");
+ }
+ }
+ throw new ValidationFailed(this, buff.toString());
+ }
+ throw new UnknownError(this, message);
+ }
+
/// Disposes of this GitHub Instance.
/// No other methods on this instance should be called after this method is called.
void dispose() {

original_message: GitHub: code reorg


message:
GitHub: Share JSON logic between postJSON and getJSON
Lot's of duplicate logic. Also applies expandos to post responses

diff:
@@ -192,33 +192,14 @@ class GitHub {
Map<String, String> headers,
Map<String, String> params,
JSONConverter<S, T> convert,
- String preview}) async {
- if (headers == null) headers = {};
-
- if (preview != null) {
- headers["Accept"] = preview;
- }
-
- if (convert == null) {
- convert = (input) => input as T;
- }
-
- headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json");
-
- var response = await request("GET", path,
- headers: headers, params: params, statusCode: statusCode, fail: fail);
-
- var json = jsonDecode(response.body);
-
- if (convert == null) {
- _applyExpandos(json, response);
- return json;
- }
-
- final returnValue = convert(json);
- _applyExpandos(returnValue, response);
- return returnValue;
- }
+ String preview}) =>
+ _requestJson('GET', path,
+ statusCode: statusCode,
+ fail: fail,
+ headers: headers,
+ params: params,
+ convert: convert,
+ preview: preview);
/// Handles Post Requests that respond with JSO
///
@@ -245,26 +226,53 @@ class GitHub {
Map<String, String> params,
JSONConverter<S, T> convert,
String body,
- String preview}) async {
- if (headers == null) headers = {};
+ String preview}) =>
+ _requestJson('POST', path,
+ statusCode: statusCode,
+ fail: fail,
+ headers: headers,
+ params: params,
+ convert: convert,
+ body: body,
+ preview: preview);
+
+ Future<T> _requestJson<S, T>(
+ String method,
+ String path, {
+ int statusCode,
+ void fail(http.Response response),
+ Map<String, String> headers,
+ Map<String, String> params,
+ JSONConverter<S, T> convert,
+ String body,
+ String preview,
+ }) async {
+ convert ??= (input) => input as T;
+ headers ??= {};
if (preview != null) {
headers["Accept"] = preview;
}
- if (convert == null) {
- convert = (input) => input as T;
- }
-
headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json");
- var response = await request("POST", path,
+ var response = await request(method, path,
headers: headers,
params: params,
body: body,
statusCode: statusCode,
fail: fail);
- return convert(jsonDecode(response.body));
+
+ var json = jsonDecode(response.body);
+
+ if (convert == null) {
+ _applyExpandos(json, response);
+ return json;
+ }
+
+ final returnValue = convert(json);
+ _applyExpandos(returnValue, response);
+ return returnValue;
}
/// Handles Authenticated Requests in an easy to understand way.


message: Remove deprecated lint

diff:
@@ -30,7 +30,6 @@ linter:
- prefer_is_not_empty
- slash_for_doc_comments
- sort_unnamed_constructors_first
- - super_goes_last
- test_types_in_equals
- throw_in_finally
- type_annotate_public_apis

original_message: Remove deprecated lint


message:
Require Dart 2.1
Since `Future` is used in a number of libraries without importing dart:async

diff:
+## v4.1.1
+
+- Require at least Dart `2.1.0`.
+
## v4.1.0
- Fix return type of `RepositoriesService.listContributors`.

diff:
name: github
-version: 4.1.0
+version: 4.1.1-dev
author: Kenneth Endfinger <kaendfinger@gmail.com>
description: GitHub API Client Library
homepage: https://github.com/DirectMyFile/github.dart
+
environment:
- sdk: '>=2.0.0 <3.0.0'
+ sdk: '>=2.1.0 <3.0.0'
+
dependencies:
http: '>=0.11.3 <0.13.0'
http_parser: ^3.1.1
json_annotation: ^2.0.0
meta: ^1.1.0
+
dev_dependencies:
build_runner: any
build_test: any


message: Add pkg:pedantic lints

diff:
+include: package:pedantic/analysis_options.yaml
linter:
rules:
- always_declare_return_types

diff:
@@ -18,5 +18,6 @@ dev_dependencies:
build_test: any
build_web_compilers: any
json_serializable: ^2.0.0
- test: ^1.3.0
mockito: ^3.0.0
+ pedantic: ^1.0.0
+ test: ^1.3.0

original_message: Add pkg:pedantic lints


message:
Breaking: fix RepositoriesService.listCollaborators
Return new type that includes the right information

diff:
+## v5.0.0
+
+- **BREAKING** `RepositoriesService.listCollaborators` now returns
+ `Stream<Collaborator>` instead of `Stream<User>`.
+ - `Collaborator` is a new type that includes collaborator-specific
+ information.
+
## v4.1.1
- Require at least Dart `2.1.0`.

diff:
@@ -86,6 +86,30 @@ class User {
}
}
+/// The response from listing collaborators on a repo.
+// https://developer.github.com/v3/repos/collaborators/#response
+@JsonSerializable(createToJson: false, fieldRename: FieldRename.snake)
+class Collaborator {
+ final String login;
+ final int id;
+ final String htmlUrl;
+ final String type;
+ final bool siteAdmin;
+ final Map<String, bool> permissions;
+
+ Collaborator(
+ this.login,
+ this.id,
+ this.htmlUrl,
+ this.type,
+ this.siteAdmin,
+ this.permissions,
+ );
+
+ factory Collaborator.fromJson(Map<String, dynamic> json) =>
+ _$CollaboratorFromJson(json);
+}
+
/// The Currently Authenticated User
@JsonSerializable(createToJson: false)
class CurrentUser extends User {

diff:
@@ -53,6 +53,18 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'updated_at': instance.updatedAt?.toIso8601String()
};
+Collaborator _$CollaboratorFromJson(Map<String, dynamic> json) {
+ return Collaborator(
+ json['login'] as String,
+ json['id'] as int,
+ json['html_url'] as String,
+ json['type'] as String,
+ json['site_admin'] as bool,
+ (json['permissions'] as Map<String, dynamic>)?.map(
+ (k, e) => MapEntry(k, e as bool),
+ ));
+}
+
CurrentUser _$CurrentUserFromJson(Map<String, dynamic> json) {
return CurrentUser()
..login = json['login'] as String

diff:
@@ -201,10 +201,12 @@ class RepositoriesService extends Service {
/// Lists the users that have access to the repository identified by [slug].
///
/// API docs: https://developer.github.com/v3/repos/collaborators/#list
- Stream<User> listCollaborators(RepositorySlug slug) {
- return PaginationHelper(_github)
- .objects("GET", "/repos/${slug.fullName}/collaborators", User.fromJson);
- }
+ Stream<Collaborator> listCollaborators(RepositorySlug slug) =>
+ PaginationHelper(_github).objects(
+ "GET",
+ "/repos/${slug.fullName}/collaborators",
+ (json) => Collaborator.fromJson(json),
+ );
Future<bool> isCollaborator(RepositorySlug slug, String user) {
return _github

diff:
name: github
-version: 4.1.1-dev
+version: 5.0.0-dev
author: Kenneth Endfinger <kaendfinger@gmail.com>
description: GitHub API Client Library
homepage: https://github.com/DirectMyFile/github.dart


message: Fix OrganizationsService.addTeamMembership

diff:
@@ -178,23 +178,11 @@ class OrganizationsService extends Service {
/// Invites a user to the specified team.
///
- /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership
- Future<TeamMembershipState> addTeamMembership(int teamId, String user) {
- var completer = Completer<TeamMembershipState>();
-
- _github.request("POST", "/teams/$teamId/memberships/$user", statusCode: 200,
- fail: (http.Response response) {
- if (response.statusCode == 404) {
- completer.complete(TeamMembershipState(null));
- } else {
- _github.handleStatusCode(response);
- }
- }).then((response) {
+ /// API docs: https://developer.github.com/v3/teams/members/#add-or-update-team-membership
+ Future<TeamMembershipState> addTeamMembership(int teamId, String user) async {
+ final response = await _github
+ .request("PUT", "/teams/$teamId/memberships/$user", statusCode: 200);
return TeamMembershipState(jsonDecode(response.body)["state"]);
- // TODO: Not sure what should go here.
- }).then(completer.complete);
-
- return completer.future;
}
/// Removes a user from the specified team.


message: fix latest pedantic lint

diff:
@@ -342,9 +342,10 @@ class GitHub {
if (statusCode != null && statusCode != response.statusCode) {
fail != null ? fail(response) : null;
handleStatusCode(response);
- } else
+ } else {
return response;
}
+ }
///
/// Internal method to handle status codes
@@ -369,8 +370,9 @@ class GitHub {
throw InvalidJSON(this, message);
} else if (message == "Body should be a JSON Hash") {
throw InvalidJSON(this, message);
- } else
+ } else {
throw BadRequest(this);
+ }
break;
case 422:
var buff = StringBuffer();

original_message: fix latest pedantic lint


message:
Update language_color_generator
refactor code
generate const map
move to /tool directory

diff:
// GENERATED CODE - DO NOT MODIFY BY HAND
-// VERSION OF 2019-06-30T14:13:12.668020
+// VERSION OF 2019-07-09T09:46:59.065183
-Map<String, String> languagesColor = <String, String>{
+const languagesColor = <String, String>{
'1C Enterprise': '#814CCC',
'ABAP': '#E8274B',
'ABNF': '#000000',

diff:
-import 'dart:io';
-
-import 'package:http/http.dart' as http;
-import 'package:yaml/yaml.dart';
-
-class LanguageColorGenerator {
- static final String indent = ' ';
- static final String path = './lib/src/const/language_color.dart';
- static final String url = 'https://raw.githubusercontent.com/'
- 'github/linguist/master/lib/linguist/languages.yml';
-
- static Future buildColorMap() async {
- http.Response response = await http.Client().get(url);
-
- YamlMap yaml = loadYaml(response.body) as YamlMap;
- StringBuffer stringBuffer = StringBuffer();
-
- stringBuffer.writeln('// GENERATED CODE - DO NOT MODIFY BY HAND');
- stringBuffer.writeln('// VERSION OF ${DateTime.now().toIso8601String()}');
- stringBuffer.writeln();
-
- stringBuffer
- .writeln('Map<String, String> languagesColor = <String, String>{');
-
- String color;
-
- YamlMap map = yaml.value;
- List<String> languages = List.from(map.keys);
- languages.sort();
-
- languages.forEach(
- (language) {
- color = map[language]['color']?.toString()?.toUpperCase() ?? '#000000';
-
- language = language.replaceAll("'", "\\'");
-
- stringBuffer.writeln("$indent'$language': '$color',");
- },
- );
-
- stringBuffer.writeln('};');
-
- File output = File(path);
-
- output.createSync();
- output.writeAsStringSync(stringBuffer.toString());
-
- print('File created with success');
- }
-}
-
-void main() {
- LanguageColorGenerator.buildColorMap();
-}

change_type: ADD

diff:
+import 'dart:io';
+
+import 'package:http/http.dart' as http;
+import 'package:yaml/yaml.dart';
+
+const _indent = ' ';
+const _path = './lib/src/const/language_color.dart';
+const _url = 'https://raw.githubusercontent.com/'
+ 'github/linguist/master/lib/linguist/languages.yml';
+
+Future main() async {
+ var response = await http.Client().get(_url);
+
+ var yaml = loadYaml(response.body) as YamlMap;
+ var stringBuffer = StringBuffer()
+ ..writeln('// GENERATED CODE - DO NOT MODIFY BY HAND')
+ ..writeln('// VERSION OF ${DateTime.now().toIso8601String()}')
+ ..writeln()
+ ..writeln('const languagesColor = <String, String>{');
+
+ var map = yaml.value as YamlMap;
+ var languages = map.keys.cast<String>().toList(growable: false)..sort();
+
+ for (var language in languages) {
+ var color = map[language]['color']?.toString()?.toUpperCase() ?? '#000000';
+
+ language = language.replaceAll("'", "\\'");
+
+ stringBuffer.writeln("$_indent'$language': '$color',");
+ }
+
+ stringBuffer.writeln('};');
+
+ File(_path)
+ ..createSync()
+ ..writeAsStringSync(stringBuffer.toString());
+
+ print('File created with success');
+}

original_message:
Update language_color_generator

- refactor code
- generate const map
- move to /tool directory


message: Latest json_serializable

diff:
@@ -9,7 +9,7 @@ part of github.common;
Map<String, dynamic> _$CreateGitBlobToJson(CreateGitBlob instance) =>
<String, dynamic>{
'content': instance.content,
- 'encoding': instance.encoding
+ 'encoding': instance.encoding,
};
GitCommit _$GitCommitFromJson(Map<String, dynamic> json) {
@@ -51,8 +51,11 @@ Map<String, dynamic> _$CreateGitCommitToJson(CreateGitCommit instance) {
}
GitCommitUser _$GitCommitUserFromJson(Map<String, dynamic> json) {
- return GitCommitUser(json['name'] as String, json['email'] as String,
- json['date'] == null ? null : DateTime.parse(json['date'] as String));
+ return GitCommitUser(
+ json['name'] as String,
+ json['email'] as String,
+ json['date'] == null ? null : DateTime.parse(json['date'] as String),
+ );
}
Map<String, dynamic> _$GitCommitUserToJson(GitCommitUser instance) {
@@ -66,8 +69,7 @@ Map<String, dynamic> _$GitCommitUserToJson(GitCommitUser instance) {
writeNotNull('name', instance.name);
writeNotNull('email', instance.email);
- writeNotNull('date',
- instance.date == null ? null : dateToGitHubIso8601(instance.date));
+ writeNotNull('date', dateToGitHubIso8601(instance.date));
return val;
}
@@ -77,10 +79,10 @@ GitTree _$GitTreeFromJson(Map<String, dynamic> json) {
json['url'] as String,
json['truncated'] as bool,
(json['tree'] as List)
- ?.map((e) => e == null
- ? null
- : GitTreeEntry.fromJson(e as Map<String, dynamic>))
- ?.toList());
+ ?.map((e) =>
+ e == null ? null : GitTreeEntry.fromJson(e as Map<String, dynamic>))
+ ?.toList(),
+ );
}
GitTreeEntry _$GitTreeEntryFromJson(Map<String, dynamic> json) {
@@ -90,7 +92,8 @@ GitTreeEntry _$GitTreeEntryFromJson(Map<String, dynamic> json) {
json['type'] as String,
json['size'] as int,
json['sha'] as String,
- json['url'] as String);
+ json['url'] as String,
+ );
}
GitReference _$GitReferenceFromJson(Map<String, dynamic> json) {
@@ -118,7 +121,10 @@ GitTag _$GitTagFromJson(Map<String, dynamic> json) {
GitObject _$GitObjectFromJson(Map<String, dynamic> json) {
return GitObject(
- json['type'] as String, json['sha'] as String, json['url'] as String);
+ json['type'] as String,
+ json['sha'] as String,
+ json['url'] as String,
+ );
}
TeamRepository _$TeamRepositoryFromJson(Map<String, dynamic> json) {
@@ -164,7 +170,10 @@ TeamRepository _$TeamRepositoryFromJson(Map<String, dynamic> json) {
TeamRepositoryPermissions _$TeamRepositoryPermissionsFromJson(
Map<String, dynamic> json) {
return TeamRepositoryPermissions(
- json['admin'] as bool, json['push'] as bool, json['pull'] as bool);
+ json['admin'] as bool,
+ json['push'] as bool,
+ json['pull'] as bool,
+ );
}
GitHubComparison _$GitHubComparisonFromJson(Map<String, dynamic> json) {
@@ -173,7 +182,8 @@ GitHubComparison _$GitHubComparisonFromJson(Map<String, dynamic> json) {
json['status'] as String,
json['ahead_by'] as int,
json['behind_by'] as int,
- json['total_commits'] as int);
+ json['total_commits'] as int,
+ );
}
Repository _$RepositoryFromJson(Map<String, dynamic> json) {
@@ -213,8 +223,12 @@ Repository _$RepositoryFromJson(Map<String, dynamic> json) {
}
CloneUrls _$CloneUrlsFromJson(Map<String, dynamic> json) {
- return CloneUrls(json['git'] as String, json['ssh'] as String,
- json['https'] as String, json['svn'] as String);
+ return CloneUrls(
+ json['git'] as String,
+ json['ssh'] as String,
+ json['https'] as String,
+ json['svn'] as String,
+ );
}
Tag _$TagFromJson(Map<String, dynamic> json) {
@@ -224,7 +238,8 @@ Tag _$TagFromJson(Map<String, dynamic> json) {
? null
: CommitInfo.fromJson(json['commit'] as Map<String, dynamic>),
json['zipball_url'] as String,
- json['tarball_url'] as String);
+ json['tarball_url'] as String,
+ );
}
CommitData _$CommitDataFromJson(Map<String, dynamic> json) {
@@ -242,14 +257,16 @@ CommitData _$CommitDataFromJson(Map<String, dynamic> json) {
json['committer'] == null
? null
: CommitDataUser.fromJson(json['committer'] as Map<String, dynamic>),
- (json['parents'] as List)
- ?.map((e) => e as Map<String, dynamic>)
- ?.toList());
+ (json['parents'] as List)?.map((e) => e as Map<String, dynamic>)?.toList(),
+ );
}
CommitDataUser _$CommitDataUserFromJson(Map<String, dynamic> json) {
return CommitDataUser(
- json['login'] as String, json['id'] as int, json['type'] as String);
+ json['login'] as String,
+ json['id'] as int,
+ json['type'] as String,
+ );
}
CommitInfo _$CommitInfoFromJson(Map<String, dynamic> json) {
@@ -257,12 +274,17 @@ CommitInfo _$CommitInfoFromJson(Map<String, dynamic> json) {
json['sha'] as String,
json['tree'] == null
? null
- : GitTree.fromJson(json['tree'] as Map<String, dynamic>));
+ : GitTree.fromJson(json['tree'] as Map<String, dynamic>),
+ );
}
UserInformation _$UserInformationFromJson(Map<String, dynamic> json) {
- return UserInformation(json['login'] as String, json['id'] as int,
- json['avatar_url'] as String, json['html_url'] as String);
+ return UserInformation(
+ json['login'] as String,
+ json['id'] as int,
+ json['avatar_url'] as String,
+ json['html_url'] as String,
+ );
}
Branch _$BranchFromJson(Map<String, dynamic> json) {
@@ -270,7 +292,8 @@ Branch _$BranchFromJson(Map<String, dynamic> json) {
json['name'] as String,
json['commit'] == null
? null
- : CommitData.fromJson(json['commit'] as Map<String, dynamic>));
+ : CommitData.fromJson(json['commit'] as Map<String, dynamic>),
+ );
}
LicenseDetails _$LicenseDetailsFromJson(Map<String, dynamic> json) {
@@ -280,9 +303,8 @@ LicenseDetails _$LicenseDetailsFromJson(Map<String, dynamic> json) {
sha: json['sha'] as String,
size: json['size'] as int,
url: json['url'] == null ? null : Uri.parse(json['url'] as String),
- htmlUrl: json['html_url'] == null
- ? null
- : Uri.parse(json['html_url'] as String),
+ htmlUrl:
+ json['html_url'] == null ? null : Uri.parse(json['html_url'] as String),
gitUrl:
json['git_url'] == null ? null : Uri.parse(json['git_url'] as String),
downloadUrl: json['download_url'] == null
@@ -296,7 +318,8 @@ LicenseDetails _$LicenseDetailsFromJson(Map<String, dynamic> json) {
: Links.fromJson(json['_links'] as Map<String, dynamic>),
license: json['license'] == null
? null
- : LicenseKind.fromJson(json['license'] as Map<String, dynamic>));
+ : LicenseKind.fromJson(json['license'] as Map<String, dynamic>),
+ );
}
Map<String, dynamic> _$LicenseDetailsToJson(LicenseDetails instance) =>
@@ -313,7 +336,7 @@ Map<String, dynamic> _$LicenseDetailsToJson(LicenseDetails instance) =>
'content': instance.content,
'encoding': instance.encoding,
'_links': instance.links,
- 'license': instance.license
+ 'license': instance.license,
};
LicenseKind _$LicenseKindFromJson(Map<String, dynamic> json) {
@@ -322,7 +345,8 @@ LicenseKind _$LicenseKindFromJson(Map<String, dynamic> json) {
name: json['name'] as String,
spdxId: json['spdx_id'] as String,
url: json['url'] == null ? null : Uri.parse(json['url'] as String),
- nodeId: json['node_id'] as String);
+ nodeId: json['node_id'] as String,
+ );
}
Map<String, dynamic> _$LicenseKindToJson(LicenseKind instance) =>
@@ -331,20 +355,21 @@ Map<String, dynamic> _$LicenseKindToJson(LicenseKind instance) =>
'name': instance.name,
'spdx_id': instance.spdxId,
'url': instance.url?.toString(),
- 'node_id': instance.nodeId
+ 'node_id': instance.nodeId,
};
Links _$LinksFromJson(Map<String, dynamic> json) {
return Links(
git: json['git'] == null ? null : Uri.parse(json['git'] as String),
self: json['self'] == null ? null : Uri.parse(json['self'] as String),
- html: json['html'] == null ? null : Uri.parse(json['html'] as String));
+ html: json['html'] == null ? null : Uri.parse(json['html'] as String),
+ );
}
Map<String, dynamic> _$LinksToJson(Links instance) => <String, dynamic>{
'self': instance.self?.toString(),
'git': instance.git?.toString(),
- 'html': instance.html?.toString()
+ 'html': instance.html?.toString(),
};
ContributorStatistics _$ContributorStatisticsFromJson(
@@ -358,22 +383,25 @@ ContributorStatistics _$ContributorStatisticsFromJson(
?.map((e) => e == null
? null
: ContributorWeekStatistics.fromJson(e as Map<String, dynamic>))
- ?.toList());
+ ?.toList(),
+ );
}
ContributorWeekStatistics _$ContributorWeekStatisticsFromJson(
Map<String, dynamic> json) {
return ContributorWeekStatistics(
- json['w'] as int, json['a'] as int, json['d'] as int, json['c'] as int);
+ json['w'] as int,
+ json['a'] as int,
+ json['d'] as int,
+ json['c'] as int,
+ );
}
CodeSearchResults _$CodeSearchResultsFromJson(Map<String, dynamic> json) {
return CodeSearchResults()
..totalCount = json['total_count'] as int
..incompleteResults = json['incomplete_results'] as bool
- ..items = json['items'] == null
- ? null
- : CodeSearchItem.fromJsonList(json['items'] as List);
+ ..items = CodeSearchItem.fromJsonList(json['items'] as List);
}
CodeSearchItem _$CodeSearchItemFromJson(Map<String, dynamic> json) {
@@ -381,12 +409,9 @@ CodeSearchItem _$CodeSearchItemFromJson(Map<String, dynamic> json) {
..name = json['name'] as String
..path = json['path'] as String
..sha = json['sha'] as String
- ..url = json['url'] == null ? null : Uri.parse(json['url'] as String)
- ..gitUrl =
- json['git_url'] == null ? null : Uri.parse(json['git_url'] as String)
- ..htmlUrl =
- json['html_url'] == null ? null : Uri.parse(json['html_url'] as String)
- ..repository = json['repository'] == null
- ? null
- : Repository.fromJSON(json['repository'] as Map<String, dynamic>);
+ ..url = Uri.parse(json['url'] as String)
+ ..gitUrl = Uri.parse(json['git_url'] as String)
+ ..htmlUrl = Uri.parse(json['html_url'] as String)
+ ..repository =
+ Repository.fromJSON(json['repository'] as Map<String, dynamic>);
}

diff:
@@ -48,12 +48,8 @@ Map<String, dynamic> _$ReleaseToJson(Release instance) => <String, dynamic>{
'prerelease': instance.prerelease,
'created_at': instance.createdAt?.toIso8601String(),
'published_at': instance.publishedAt?.toIso8601String(),
- 'author': instance.author == null
- ? null
- : Release._authorToJson(instance.author),
- 'assets': instance.assets == null
- ? null
- : Release._assetsToJson(instance.assets)
+ 'author': Release._authorToJson(instance.author),
+ 'assets': Release._assetsToJson(instance.assets),
};
ReleaseAsset _$ReleaseAssetFromJson(Map<String, dynamic> json) {
@@ -85,11 +81,13 @@ Map<String, dynamic> _$ReleaseAssetToJson(ReleaseAsset instance) =>
'size': instance.size,
'download_count': instance.downloadCount,
'created_at': instance.createdAt?.toIso8601String(),
- 'updated_at': instance.updatedAt?.toIso8601String()
+ 'updated_at': instance.updatedAt?.toIso8601String(),
};
CreateRelease _$CreateReleaseFromJson(Map<String, dynamic> json) {
- return CreateRelease(json['tag_name'] as String)
+ return CreateRelease(
+ json['tag_name'] as String,
+ )
..json = json['json'] as Map<String, dynamic>
..targetCommitish = json['target_commitish'] as String
..name = json['name'] as String
@@ -106,5 +104,5 @@ Map<String, dynamic> _$CreateReleaseToJson(CreateRelease instance) =>
'name': instance.name,
'body': instance.body,
'draft': instance.draft,
- 'prerelease': instance.prerelease
+ 'prerelease': instance.prerelease,
};

diff:
@@ -50,7 +50,7 @@ Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'followers': instance.followersCount,
'following': instance.followingCount,
'created_at': instance.createdAt?.toIso8601String(),
- 'updated_at': instance.updatedAt?.toIso8601String()
+ 'updated_at': instance.updatedAt?.toIso8601String(),
};
Collaborator _$CollaboratorFromJson(Map<String, dynamic> json) {
@@ -62,7 +62,8 @@ Collaborator _$CollaboratorFromJson(Map<String, dynamic> json) {
json['site_admin'] as bool,
(json['permissions'] as Map<String, dynamic>)?.map(
(k, e) => MapEntry(k, e as bool),
- ));
+ ),
+ );
}
CurrentUser _$CurrentUserFromJson(Map<String, dynamic> json) {

diff:
@@ -17,7 +17,7 @@ dev_dependencies:
build_runner: any
build_test: any
build_web_compilers: any
- json_serializable: ^2.0.0
+ json_serializable: ^3.1.0
mockito: ^3.0.0
pedantic: ^1.0.0
test: ^1.3.0

original_message: Latest json_serializable


message: Repository: added updatedAt and license fields

diff:
+## v5.0.3
+
+- `Repository`: added `updatedAt` and `license` fields.
+
## v5.0.2
- Fixed pollPublicEventsReceivedByUser to use the correct API URL https://github.com/DirectMyFile/github.dart/pull/150

diff:
@@ -161,6 +161,12 @@ TeamRepository _$TeamRepositoryFromJson(Map<String, dynamic> json) {
..pushedAt = json['pushed_at'] == null
? null
: DateTime.parse(json['pushed_at'] as String)
+ ..updatedAt = json['updated_at'] == null
+ ? null
+ : DateTime.parse(json['updated_at'] as String)
+ ..license = json['license'] == null
+ ? null
+ : LicenseKind.fromJson(json['license'] as Map<String, dynamic>)
..permissions = json['permissions'] == null
? null
: TeamRepositoryPermissions.fromJson(
@@ -219,7 +225,13 @@ Repository _$RepositoryFromJson(Map<String, dynamic> json) {
: DateTime.parse(json['created_at'] as String)
..pushedAt = json['pushed_at'] == null
? null
- : DateTime.parse(json['pushed_at'] as String);
+ : DateTime.parse(json['pushed_at'] as String)
+ ..updatedAt = json['updated_at'] == null
+ ? null
+ : DateTime.parse(json['updated_at'] as String)
+ ..license = json['license'] == null
+ ? null
+ : LicenseKind.fromJson(json['license'] as Map<String, dynamic>);
}
CloneUrls _$CloneUrlsFromJson(Map<String, dynamic> json) {

diff:
@@ -122,6 +122,11 @@ class Repository {
@JsonKey(name: "pushed_at")
DateTime pushedAt;
+ @JsonKey(name: 'updated_at')
+ DateTime updatedAt;
+
+ LicenseKind license;
+
static Repository fromJSON(Map<String, dynamic> input) {
if (input == null) return null;
return _$RepositoryFromJson(input);

diff:
name: github
-version: 5.0.2
+version: 5.0.3-dev
author: Kenneth Endfinger <kaendfinger@gmail.com>
description: GitHub API Client Library
homepage: https://github.com/DirectMyFile/github.dart


message: bump version and SDK constraint

diff:
-## v5.0.3
+## v5.1.0
- `Repository`: added `updatedAt` and `license` fields.
+ - Require at least Dart `2.2.0`.
## v5.0.2
- Fixed pollPublicEventsReceivedByUser to use the correct API URL https://github.com/DirectMyFile/github.dart/pull/150

diff:
name: github
-version: 5.0.3-dev
+version: 5.1.0-dev
author: Kenneth Endfinger <kaendfinger@gmail.com>
description: GitHub API Client Library
homepage: https://github.com/DirectMyFile/github.dart
environment:
- sdk: '>=2.1.0 <3.0.0'
+ sdk: '>=2.2.0 <3.0.0'
dependencies:
http: '>=0.11.3 <0.13.0'

original_message: bump version and SDK constraint


message: Fix GitHubFile.text decoding

diff:
+## v6.0.2
+ - Fixed `GitHubFile.text` to properly decode `content`.
+
## v6.0.1
- Fix https://github.com/SpinlockLabs/github.dart/issues/190
- removed deprecated items
- renamed some fields with ID at the end to be Id
- most model constructors now have named parameters for all properties
+- `GitHubFile.content` is now exactly the content returned from the JSON API
+ without newlines removed.
## v5.5.0

diff:
@@ -37,7 +37,7 @@ class GitHubFile {
/// File Path
String path;
- /// File Content
+ /// Base-64 encoded file content with newlines.
String content;
/// SHA
@@ -55,10 +55,10 @@ class GitHubFile {
@JsonKey(name: '_links')
Links links;
- /// Text Content
+ /// The value in [content] Base-64 decoded.
String get text {
if (_text == null) {
- _text = utf8.decode(base64Decode(content));
+ _text = utf8.decode(base64Decode(LineSplitter.split(content).join()));
}
return _text;
}

diff:
name: github
-version: 6.0.1
+version: 6.0.2
author: Kenneth Endfinger <kaendfinger@gmail.com>
description: A high-level GitHub API Client Library that uses Github's v3 API
homepage: https://github.com/SpinlockLabs/github.dart

original_message: Fix GitHubFile.text decoding


message: Centralize definition of v3 API mime type

diff:
@@ -229,7 +229,7 @@ class GitHub {
headers['Accept'] = preview;
}
- headers.putIfAbsent('Accept', () => 'application/vnd.github.v3+json');
+ headers.putIfAbsent('Accept', () => v3ApiMimeType);
final response = await request(
method,

diff:
@@ -1140,8 +1140,8 @@ class RepositoriesService extends Service {
) async {
ArgumentError.checkNotNull(slug);
final path = '/repos/${slug.fullName}/stats/contributors';
- final response = await github.request('GET', path,
- headers: {'Accept': 'application/vnd.github.v3+json'});
+ final response =
+ await github.request('GET', path, headers: {'Accept': v3ApiMimeType});
if (response.statusCode == StatusCodes.OK) {
return (jsonDecode(response.body) as List)

diff:
@@ -4,6 +4,7 @@ import 'dart:convert' show jsonDecode;
import 'package:http/http.dart' as http;
import '../../common.dart';
+import '../../util.dart';
/// Internal Helper for dealing with GitHub Pagination.
class PaginationHelper {
@@ -93,7 +94,7 @@ class PaginationHelper {
if (preview != null) {
headers['Accept'] = preview;
}
- headers.putIfAbsent('Accept', () => 'application/vnd.github.v3+json');
+ headers.putIfAbsent('Accept', () => v3ApiMimeType);
await for (final response in fetchStreamed(method, path,
pages: pages,

diff:
final RegExp githubDateRemoveRegExp = RegExp(r'\.\d*');
+const v3ApiMimeType = 'application/vnd.github.v3+json';
+
String buildQueryString(Map<String, dynamic> params) {
final queryString = StringBuffer();


message: Add IssueService.listReactions function

diff:
+## 6.1.0
+ - Add (experimental) `listReactions` method to `IssueService`.
+
## 6.0.6
- Clean up lints https://github.com/SpinlockLabs/github.dart/pull/202

diff:
@@ -17,6 +17,7 @@ export 'package:github/src/common/model/misc.dart';
export 'package:github/src/common/model/notifications.dart';
export 'package:github/src/common/model/orgs.dart';
export 'package:github/src/common/model/pulls.dart';
+export 'package:github/src/common/model/reaction.dart';
export 'package:github/src/common/model/repos.dart';
export 'package:github/src/common/model/repos_commits.dart';
export 'package:github/src/common/model/repos_contents.dart';

diff:
import 'dart:async';
import 'dart:convert';
+
import 'package:github/src/common.dart';
import 'package:github/src/common/model/users.dart';
import 'package:github/src/common/util/pagination.dart';
@@ -129,6 +130,19 @@ class IssuesService extends Service {
);
}
+ /// This API is currently in preview. It may break.
+ ///
+ /// See https://developer.github.com/v3/reactions/
+ Stream<Reaction> listReactions(RepositorySlug slug, int issueNumber) =>
+ PaginationHelper(github).objects(
+ 'GET',
+ '/repos/${slug.owner}/${slug.name}/issues/$issueNumber/reactions',
+ (i) => Reaction.fromJson(i),
+ headers: {
+ 'Accept': 'application/vnd.github.squirrel-girl-preview+json',
+ },
+ );
+
/// Edit an issue.
///
/// API docs: https://developer.github.com/v3/issues/#edit-an-issue

diff:
+import 'package:github/src/common.dart';
+import 'package:github/src/common/model/users.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'reaction.g.dart';
+
+/// This API is currently in preview. It may break.
+///
+/// See https://developer.github.com/v3/reactions/
+@JsonSerializable(fieldRename: FieldRename.snake)
+class Reaction {
+ final int id;
+ final String nodeId;
+ final User user;
+ final String content;
+ final DateTime createdAt;
+
+ Reaction({
+ this.id,
+ this.nodeId,
+ this.user,
+ this.content,
+ this.createdAt,
+ });
+
+ factory Reaction.fromJson(Map<String, dynamic> json) =>
+ _$ReactionFromJson(json);
+
+ Map<String, dynamic> toJson() => _$ReactionToJson(this);
+}

diff:
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'reaction.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+Reaction _$ReactionFromJson(Map<String, dynamic> json) {
+ return Reaction(
+ id: json['id'] as int,
+ nodeId: json['node_id'] as String,
+ user: json['user'] == null
+ ? null
+ : User.fromJson(json['user'] as Map<String, dynamic>),
+ content: json['content'] as String,
+ createdAt: json['created_at'] == null
+ ? null
+ : DateTime.parse(json['created_at'] as String),
+ );
+}
+
+Map<String, dynamic> _$ReactionToJson(Reaction instance) => <String, dynamic>{
+ 'id': instance.id,
+ 'node_id': instance.nodeId,
+ 'user': instance.user,
+ 'content': instance.content,
+ 'created_at': instance.createdAt?.toIso8601String(),
+ };

diff:
name: github
-version: 6.0.6
+version: 6.1.0-dev
description: A high-level GitHub API Client Library that uses Github's v3 API
homepage: https://github.com/SpinlockLabs/github.dart

original_message: Add IssueService.listReactions function


message: Update lib/src/common/model/issues.dart

diff:
@@ -5,7 +5,7 @@ import 'package:json_annotation/json_annotation.dart';
part 'issues.g.dart';
/// Model class for an issue on the tracker.
-@JsonSerializable(fieldRename: FieldRename.snake)
+@JsonSerializable()
class Issue {
Issue({
this.id = 0,

original_message:
Update lib/src/common/model/issues.dart

Co-authored-by: Rob Becker <rob.becker@workiva.com>


message: [RMA] Feature Optimize ObjectFlusher test.

diff:
@@ -40,8 +40,10 @@ public class ObjectFlusher<T extends IMeasurable> implements IObjectFlusher<T> {
private final Logger logger = LoggerFactory.getLogger(getClass().getName() + "@" + Integer.toHexString(hashCode()));
private static final Logger USER_EVENTS_LOG = CommonLoggers.USER_EVENTS_LOGGER;
+ private static final long DEFAULT_FLUSH_TASK_TIMEOUT = 1000;
private static final long JOIN_TIMEOUT = 2000;
+
private final Lock monitor = new ReentrantLock(true);
private final ReadWriteLock sourceMonitor = new ReentrantReadWriteLock();
private final Condition needFlush = monitor.newCondition();
@@ -49,6 +51,7 @@ public class ObjectFlusher<T extends IMeasurable> implements IObjectFlusher<T> {
private final int bufferSize;
private final long maxStorageQueueSize;
+ private final long flushTaskTimeout;
private long storageProviderQueueSize;
private long lastThrottleNotification;
@@ -104,10 +107,15 @@ public class ObjectFlusher<T extends IMeasurable> implements IObjectFlusher<T> {
}
public ObjectFlusher(IFlushProvider<T> provider, int bufferSize, long maxStorageQueueSize) {
+ this(provider, bufferSize, maxStorageQueueSize, DEFAULT_FLUSH_TASK_TIMEOUT);
+ }
+
+ public ObjectFlusher(IFlushProvider<T> provider, int bufferSize, long maxStorageQueueSize, long flushTaskTimeout) {
this.provider = Objects.requireNonNull(provider, "provider cannot be null");
this.bufferSize = bufferSize;
this.objects = new ArrayList<>(bufferSize + 1);
this.maxStorageQueueSize = maxStorageQueueSize;
+ this.flushTaskTimeout = flushTaskTimeout;
}
@Override
@@ -118,7 +126,7 @@ public class ObjectFlusher<T extends IMeasurable> implements IObjectFlusher<T> {
throw new EPSCommonException("Cannot start flusher. Flusher is already started");
}
- flushTask = new FlushTask();
+ flushTask = new FlushTask(flushTaskTimeout);
flushThread = new Thread(flushTask, logger.getName());
flushThread.setDaemon(true);
flushThread.start();
@@ -230,7 +238,11 @@ public class ObjectFlusher<T extends IMeasurable> implements IObjectFlusher<T> {
}
public class FlushTask implements Runnable {
- private static final long TIMEOUT = 1000;
+ private final long timeout;
+
+ public FlushTask(long timeout) {
+ this.timeout = timeout;
+ }
@Override
public void run() {
@@ -258,7 +270,7 @@ public class ObjectFlusher<T extends IMeasurable> implements IObjectFlusher<T> {
logger.debug("monitor locked");
if (wait) {
- needFlush.await(TIMEOUT, TimeUnit.MILLISECONDS);
+ needFlush.await(timeout, TimeUnit.MILLISECONDS);
}
temp = objects;

diff:
package com.exactpro.sf;
+import java.io.Closeable;
+import java.util.concurrent.atomic.AtomicInteger;
+
import org.junit.Assert;
import org.junit.Test;
-
-import java.io.Closeable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.exactpro.sf.storage.IMeasurable;
import com.exactpro.sf.storage.impl.ObjectFlusher;
public class TestObjectFlusher {
- private static final long timeToRun = 1000 * 10;
+ private static final int COUNT_ELEMENTS = 32;
+ private static final int SIZE_OF_OBJECT = 1024 * 1024;
+ private static final int BUFFER_SIZE = COUNT_ELEMENTS * 2;
+ private static final int ADD_ELEMENTS_FACTOR = 64;
+ private static final long TASK_TIMEOUT = 5;
+
+ private static final Logger logger = LoggerFactory.getLogger(TestObjectFlusher.class);
@Test
public void testOOM() throws Exception {
- ObjectFlusher<OmNomNom> objectFlusher = new ObjectFlusher<>(o -> {
- System.out.println("try to flush objects " + o.size());
- Assert.assertEquals(32, o.size());//check that limit is not exceeded
- System.out.println("objects flushed");
- }, 32*2, 32*1024*1024); //flush thereshold is 64 but limit lower (only 32 objects)
-
- try (Closeable closeable = objectFlusher::stop) {
+ AtomicInteger consumerCounter = new AtomicInteger();
+ ObjectFlusher<OmNomNom> objectFlusher = new ObjectFlusher<>(
+ list -> consumerCounter.addAndGet(list.size()),
+ COUNT_ELEMENTS * 2,
+ COUNT_ELEMENTS * SIZE_OF_OBJECT,
+ TASK_TIMEOUT
+ );
- long startTime = System.currentTimeMillis();
objectFlusher.start();
- while (System.currentTimeMillis() - startTime <= timeToRun) {
+
+ try (Closeable closeable = objectFlusher::stop) {
+ for (int i = 0; i < BUFFER_SIZE * ADD_ELEMENTS_FACTOR; i++) {
objectFlusher.add(new OmNomNom());
}
}
+ logger.debug("Counting elements: %d. Max elements: %d", consumerCounter.get(), BUFFER_SIZE * ADD_ELEMENTS_FACTOR);
+ System.out.println(consumerCounter.get());
+ Assert.assertTrue(consumerCounter.get() < BUFFER_SIZE * ADD_ELEMENTS_FACTOR);
}
private class OmNomNom implements IMeasurable {
- private byte[] eatRam = new byte[1024*1024];
-
@Override
public long getSize() {
- return eatRam.length;
- }
-
- public byte[] getEatRam() {
- return eatRam;
- }
-
- public void setEatRam(byte[] eatRam) {
- this.eatRam = eatRam;
+ return SIZE_OF_OBJECT;
}
}

license: Apache License 2.0

repo: exactpro/sailfish-core

original_message:
[RMA] Feature #49122. Optimize ObjectFlusher test.

Change-Id: I0ddd7e06e53c06d9fba4d8d4940b09656c8876cc


message: [RM] Feature Pack services settings which used in matrix into report archive.

diff:
@@ -26,6 +26,7 @@ import java.util.Set;
import org.apache.commons.beanutils.BeanMap;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtilsBean;
+import org.apache.commons.beanutils.Converter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.WordUtils;
@@ -57,10 +58,27 @@ public class ServiceActions extends AbstractCaller {
//TODO: change to service_uri someday
public final String SERVICE_TYPE = "service_type";
+ private static class SailfishURIConverter implements Converter {
+ @Override
+ public <T> T convert(Class<T> type, Object value) {
+ if (value instanceof String) {
+ try {
+ return (T) SailfishURI.parse((String) value);
+ } catch (SailfishURIException e) {
+ throw new EPSCommonException("Wrong value [" + value + "] for parse to SailfishURI", e);
+ }
+ }
+
+ throw new EPSCommonException("Wrong value [" + value + "] for parse to SailfishURI");
+ }
+ }
+
private final ThreadLocal<ConvertUtilsBean> converter = new ThreadLocal<ConvertUtilsBean>() {
@Override
protected ConvertUtilsBean initialValue() {
- return new ConvertUtilsBean();
+ ConvertUtilsBean convertUtilsBean = new ConvertUtilsBean();
+ convertUtilsBean.register(new SailfishURIConverter(), SailfishURI.class);
+ return convertUtilsBean;
}
};
@@ -146,12 +164,14 @@ public class ServiceActions extends AbstractCaller {
try {
IServiceSettings serviceSettings = serviceManager.getServiceSettings(serviceName);
BeanMap beanMap = new BeanMap(serviceSettings);
+ Set<String> editedProperty = new HashSet<>();
Set<String> incorrectProperty = new HashSet<>();
for (Entry<?, ?> entry : message.entrySet()) {
String property = convertProperty(entry.getKey().toString());
if (beanMap.containsKey(property)){
try {
BeanUtils.setProperty(serviceSettings, property, converter.get().convert((Object)unwrapFilters(entry.getValue()), beanMap.getType(property)));
+ editedProperty.add(property);
} catch (IllegalAccessException e) {
throw new EPSCommonException(e);
} catch (InvocationTargetException e) {
@@ -169,6 +189,8 @@ public class ServiceActions extends AbstractCaller {
serviceManager.updateService(serviceName, serviceSettings, null);
serviceManager.initService(serviceName, null).get();
+
+ actionContext.getReport().createChangingSettingsFile(serviceName.toString(), serviceSettings, editedProperty);
} catch (EPSCommonException e) {
throw e;
} catch (Exception e){

diff:
@@ -23,6 +23,7 @@ import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
@@ -43,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.exactpro.sf.common.util.Pair;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
@@ -185,6 +187,36 @@ public class DefaultWorkspaceDispatcher implements IWorkspaceDispatcher {
}
}
+ public File getOrCreateFolder(FolderType folderType, String... folderName) throws WorkspaceStructureException, WorkspaceSecurityException, FileNotFoundException {
+ checkFolderTypeAndFileName(folderType, folderName);
+
+ try {
+ workspacesLock.readLock().lock();
+ File targetFolder = new File(Iterables.getLast(workspaces).get(folderType), toPathString(folderName));
+
+ if (targetFolder.exists()) {
+ if (targetFolder.isDirectory()) {
+ return targetFolder;
+ } else {
+ throw new FileNotFoundException(MessageFormat.format("Path '{'{0}'}' is not folder", targetFolder.toPath()));
+ }
+ }
+
+ WorkspaceLayer workspaceLayer = workspaces.get(workspaces.size() - 1);
+ if (workspaceLayer.layout.isEmbedded()) {
+ throw new WorkspaceLayerException(MessageFormat.format("Folder '{'{0}'}'/{1} can'''t be created, the last layer is embedded", folderType, toPathString(folderName)));
+ }
+
+ if (targetFolder.mkdirs()) {
+ return targetFolder;
+ }
+
+ throw new WorkspaceLayerException(MessageFormat.format("Folder '{'{0}'}'/{1} can'''t be created", folderType, toPathString(folderName)));
+ } finally {
+ workspacesLock.readLock().unlock();
+ }
+ }
+
@Override
public File createFolder(FolderType folderType, String... folderName) throws WorkspaceStructureException {
checkFolderTypeAndFileName(folderType, folderName);

diff:
@@ -19,9 +19,7 @@ import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.util.Objects;
import java.util.Set;
-import java.util.stream.Collectors;
/**
* Implements UnionMount[1]-like mechanism for SF
@@ -89,6 +87,8 @@ public interface IWorkspaceDispatcher {
*/
File getOrCreateFile(FolderType folderType, String... fileName) throws WorkspaceStructureException, WorkspaceSecurityException, FileNotFoundException;
+ File getOrCreateFolder(FolderType folderType, String... folderName) throws WorkspaceStructureException, WorkspaceSecurityException, FileNotFoundException;
+
boolean exists(FolderType folderType, String... fileName) throws WorkspaceSecurityException;
void removeFile(FolderType folderType, String... fileName) throws IOException, WorkspaceSecurityException;

diff:
@@ -72,6 +72,8 @@ import com.exactpro.sf.scriptrunner.TestScriptDescription.ScriptState;
import com.exactpro.sf.scriptrunner.TestScriptDescription.ScriptStatus;
import com.exactpro.sf.scriptrunner.actionmanager.IActionManager;
import com.exactpro.sf.scriptrunner.impl.BroadcastScriptReport;
+import com.exactpro.sf.scriptrunner.impl.ReportDictionaries;
+import com.exactpro.sf.scriptrunner.impl.ReportServices;
import com.exactpro.sf.scriptrunner.impl.ScriptReportWithLogs;
import com.exactpro.sf.scriptrunner.impl.StatisticScriptReport;
import com.exactpro.sf.scriptrunner.impl.htmlreport.HtmlReport;
@@ -263,6 +265,10 @@ public abstract class AbstractScriptRunner implements IDisposable {
aggregateReportListeners.add(new JsonReport(reportFolder, workspaceDispatcher, scriptDescription, dictionaryManager));
// html report
aggregateReportListeners.add(new HtmlReport(sfContext.getSfInstanceInfo(), reportFolder, workspaceDispatcher, dictionaryManager, environmentManager.getEnvironmentSettings().getRelevantMessagesSortingMode()));
+ //dictionary report
+ aggregateReportListeners.add(new ReportDictionaries(reportFolder));
+ //services report
+ aggregateReportListeners.add(new ReportServices(staticServiceManager, reportFolder));
BroadcastScriptReport aggregateReport = new BroadcastScriptReport(aggregateReportListeners);
@@ -272,11 +278,8 @@ public abstract class AbstractScriptRunner implements IDisposable {
reportListeners.addAll(userListeners);
}
- ReportOutputFormat reportOutputFormat = environmentManager.getEnvironmentSettings().getReportOutputFormat();
- if(reportOutputFormat.isEnableZip()) {
// NOTE: ZipReport must be latest
- reportListeners.add(new ZipReport(reportFolder, workspaceDispatcher, scriptDescription, reportOutputFormat));
- }
+ reportListeners.add(new ZipReport(reportFolder, workspaceDispatcher, scriptDescription, ReportOutputFormat.ZIP));
BroadcastScriptReport report = new BroadcastScriptReport(reportListeners);

diff:
@@ -169,10 +169,10 @@ public class ZipReport implements IScriptReport {
private void zipFilesInFolder(File reportFolder, String zipName, IWorkspaceDispatcher dispatcher) throws IOException {
- File report = dispatcher.createFile(FolderType.REPORT, true, zipName, zipName + ZIP);
+ File report = dispatcher.createFile(FolderType.REPORT, true, zipName + ZIP);
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(report))) {
- zip.setLevel(Deflater.DEFAULT_COMPRESSION);
+ zip.setLevel(Deflater.BEST_COMPRESSION);
zipFolder(dispatcher, zip, zipName, null);
}
@@ -222,5 +222,6 @@ public class ZipReport implements IScriptReport {
dispatcher.removeFile(FolderType.REPORT, reportFolderName, file);
}
}
+ dispatcher.removeFolder(FolderType.REPORT, reportFolderName);
}
}

diff:
******************************************************************************/
package com.exactpro.sf.scriptrunner.actionmanager.actioncontext;
+import com.exactpro.sf.services.IServiceSettings;
import java.io.File;
import com.exactpro.sf.common.messages.IMessage;
@@ -27,6 +28,10 @@ import com.exactpro.sf.scriptrunner.StatusType;
import com.exactpro.sf.scriptrunner.impl.ReportTable;
import com.exactpro.sf.scriptrunner.reportbuilder.textformatter.TextColor;
import com.exactpro.sf.scriptrunner.reportbuilder.textformatter.TextStyle;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Set;
public interface IActionReport extends AutoCloseable {
IActionReport createEmbeddedReport(String name, String description);
@@ -52,6 +57,9 @@ public interface IActionReport extends AutoCloseable {
void createParametersTable(IMessage message);
+ void createChangingSettingsFile(String serviceName, IServiceSettings serviceSettings, Set<String> editedProperties)
+ throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException;
+
File createFile(StatusType status, String... pathElements)
throws WorkspaceStructureException, WorkspaceSecurityException;

diff:
******************************************************************************/
package com.exactpro.sf.scriptrunner.actionmanager.actioncontext.impl;
+import com.exactpro.sf.services.IServiceSettings;
import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
@@ -40,8 +46,13 @@ import com.exactpro.sf.scriptrunner.actionmanager.actioncontext.IActionReport;
import com.exactpro.sf.scriptrunner.impl.ReportTable;
import com.exactpro.sf.scriptrunner.reportbuilder.textformatter.TextColor;
import com.exactpro.sf.scriptrunner.reportbuilder.textformatter.TextStyle;
+import com.exactpro.sf.util.DateTimeUtility;
public class ActionReport implements IActionReport {
+
+ private static final DateTimeFormatter FORMATTER = DateTimeUtility.createFormatter("dd.MM.yyyy HH:mm:ss:SSS");
+ private static final String changingSettingFolder = "changingSetting";
+
private final IScriptReport report;
private final String reportFolder;
private final boolean updateStatus;
@@ -133,6 +144,23 @@ public class ActionReport implements IActionReport {
return workspaceDispatcher.createFile(FolderType.REPORT, false, ArrayUtils.insert(0, pathElements, reportFolder));
}
+ @Override
+ public void createChangingSettingsFile(String serviceName, IServiceSettings serviceSettings, Set<String> editedProperties) throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+ File changingSettingsFile = workspaceDispatcher.getOrCreateFile(FolderType.REPORT, reportFolder, changingSettingFolder, serviceName);
+
+ try (FileWriter fileWriter = new FileWriter(changingSettingsFile, true)) {
+ fileWriter.write(String.format("Date and time: {%s}\nProperties:\n", FORMATTER.format(DateTimeUtility.nowLocalDateTime())));
+
+ for (String propertyName : editedProperties) {
+
+ Object value = BeanUtils.getProperty(serviceSettings, propertyName);
+
+ fileWriter.write(String.format("\'%s\'={%s}\n", propertyName, value == null ? "NULL" : value.toString()));
+ }
+ fileWriter.write('\n');
+ }
+ }
+
@Override
public void close() {
if(child != null) {

diff:
+/******************************************************************************
+ * Copyright 2009-2018 Exactpro (Exactpro Systems Limited)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package com.exactpro.sf.scriptrunner.impl;
+
+import com.exactpro.sf.aml.AMLBlockType;
+import com.exactpro.sf.aml.generator.AggregateAlert;
+import com.exactpro.sf.aml.script.CheckPoint;
+import com.exactpro.sf.common.messages.IMessage;
+import com.exactpro.sf.common.services.ServiceName;
+import com.exactpro.sf.common.util.EPSCommonException;
+import com.exactpro.sf.comparison.ComparisonResult;
+import com.exactpro.sf.configuration.suri.SailfishURI;
+import com.exactpro.sf.configuration.workspace.FolderType;
+import com.exactpro.sf.configuration.workspace.WorkspaceStructureException;
+import com.exactpro.sf.scriptrunner.IConnectionManager;
+import com.exactpro.sf.scriptrunner.IReportStats;
+import com.exactpro.sf.scriptrunner.IScriptReport;
+import com.exactpro.sf.scriptrunner.LoggerRow;
+import com.exactpro.sf.scriptrunner.MessageLevel;
+import com.exactpro.sf.scriptrunner.OutcomeCollector;
+import com.exactpro.sf.scriptrunner.ScriptContext;
+import com.exactpro.sf.scriptrunner.StatusDescription;
+import com.exactpro.sf.services.IServiceSettings;
+import com.exactpro.sf.services.ServiceDescription;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+
+public class ReportDictionaries implements IScriptReport {
+
+ private final String dictionariesFolder;
+
+ public ReportDictionaries(String reportFolder){
+ dictionariesFolder = Paths.get(reportFolder, "dictionaries").toString();
+ }
+
+ @Override
+ public void createReport(ScriptContext scriptContext, String name, String description, long scriptRunId, String environmentName, String userName) {
+
+ File dictionariesFolderFile;
+ try {
+ dictionariesFolderFile = scriptContext.getWorkspaceDispatcher().createFolder(FolderType.REPORT, dictionariesFolder);
+ } catch (WorkspaceStructureException e) {
+ throw new EPSCommonException("Can`t create dictionaries folder", e);
+ }
+
+ Set<SailfishURI> dictionariesNames = new HashSet<>();
+ List<String> matrixServicesName = scriptContext.getServiceList();
+ IConnectionManager connectionManager = scriptContext.getEnvironmentManager().getConnectionManager();
+
+ for (String serviceName : matrixServicesName) {
+ ServiceDescription serviceDescription = connectionManager.getServiceDescription(ServiceName.parse(serviceName));
+
+ IServiceSettings serviceSettings = serviceDescription.getSettings();
+ SailfishURI dictionaryInfo = serviceSettings.getDictionaryName();
+ if (dictionaryInfo == null || !dictionariesNames.add(dictionaryInfo)) {
+ continue;
+ }
+
+ String dictionaryPath = scriptContext.getDictionaryManager().getDictionaryLocations().get(dictionaryInfo);
+
+ if (dictionaryPath == null) {
+ continue;
+ }
+
+ File srcDictionaryFile;
+ try {
+ srcDictionaryFile = scriptContext.getWorkspaceDispatcher().getFile(FolderType.ROOT, dictionaryPath);
+ } catch (FileNotFoundException e) {
+ throw new EPSCommonException("Can`t get dictionary file", e);
+ }
+
+ File targetDictionaryDirectory = dictionariesFolderFile;
+ if (!StringUtils.isEmpty(dictionaryInfo.getPluginAlias())) {
+ try {
+ targetDictionaryDirectory = scriptContext.getWorkspaceDispatcher().getOrCreateFolder(FolderType.REPORT, dictionariesFolder, dictionaryInfo.getPluginAlias());
+ } catch (WorkspaceStructureException | FileNotFoundException e) {
+ throw new EPSCommonException("Can`t create dictionaries plugin folder", e);
+ }
+ }
+
+ try {
+ FileUtils.copyFileToDirectory(srcDictionaryFile, targetDictionaryDirectory);
+ } catch (IOException e) {
+ throw new EPSCommonException("Can`t copy dictionary file to target folder", e);
+ }
+ }
+ }
+
+ @Override
+ public void addAlerts(Collection<AggregateAlert> aggregatedAlerts) {
+ }
+
+ @Override
+ public void closeReport() {
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void createTestCase(String reference, String description, int order, int matrixOrder, String tcId, int tcHash, AMLBlockType type, Set<String> tags) {
+ }
+
+ @Override
+ public void closeTestCase(StatusDescription status) {
+ }
+
+ @Override
+ public boolean isActionCreated() throws UnsupportedOperationException {
+ return false;
+ }
+
+ @Override
+ public void createAction(String id, String serviceName, String name, String messageType, String description, IMessage parameters, CheckPoint checkPoint, String tag, int hash, List<String> verificationsOrder, String outcome) {
+ }
+
+ @Override
+ public void closeAction(StatusDescription status, Object actionResult) {
+ }
+
+ @Override
+ public void openGroup(String name, String description) {
+ }
+
+ @Override
+ public void closeGroup(StatusDescription status) {
+ }
+
+ @Override
+ public void createVerification(String name, String description, StatusDescription status, ComparisonResult result) {
+ }
+
+ @Override
+ public void createMessage(MessageLevel level, String... messages) {
+ }
+
+ @Override
+ public void createMessage(MessageLevel level, Throwable e, String... messages) {
+ }
+
+ @Override
+ public void createException(Throwable cause) {
+ }
+
+ @Override
+ public void createTable(ReportTable table) {
+ }
+
+ @Override
+ public void createLogTable(List<String> header, List<LoggerRow> rows) {
+ }
+
+ @Override
+ public void setOutcomes(OutcomeCollector outcomes) {
+ }
+
+ @Override
+ public void createLinkToReport(String linkToReport) {
+ }
+
+ @Override
+ public IReportStats getReportStats() {
+ return null;
+ }
+}

diff:
+/******************************************************************************
+ * Copyright 2009-2018 Exactpro (Exactpro Systems Limited)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package com.exactpro.sf.scriptrunner.impl;
+
+import com.exactpro.sf.aml.AMLBlockType;
+import com.exactpro.sf.aml.generator.AggregateAlert;
+import com.exactpro.sf.aml.script.CheckPoint;
+import com.exactpro.sf.common.messages.IMessage;
+import com.exactpro.sf.common.util.EPSCommonException;
+import com.exactpro.sf.comparison.ComparisonResult;
+import com.exactpro.sf.configuration.workspace.FolderType;
+import com.exactpro.sf.configuration.workspace.WorkspaceStructureException;
+import com.exactpro.sf.scriptrunner.IReportStats;
+import com.exactpro.sf.scriptrunner.IScriptReport;
+import com.exactpro.sf.scriptrunner.LoggerRow;
+import com.exactpro.sf.scriptrunner.MessageLevel;
+import com.exactpro.sf.scriptrunner.OutcomeCollector;
+import com.exactpro.sf.scriptrunner.ScriptContext;
+import com.exactpro.sf.scriptrunner.StatusDescription;
+import com.exactpro.sf.scriptrunner.services.IStaticServiceManager;
+import com.exactpro.sf.services.ServiceMarshalManager;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class ReportServices implements IScriptReport {
+
+ private final IStaticServiceManager staticServiceManager;
+ private final String reportFolder;
+
+ public ReportServices(IStaticServiceManager staticServiceManager, String reportFolder) {
+ this.staticServiceManager = staticServiceManager;
+ this.reportFolder = reportFolder;
+ }
+
+ @Override
+ public void createReport(ScriptContext scriptContext, String name, String description, long scriptRunId, String environmentName, String userName) {
+
+ // Code for export service coped from ServiceMarshalManager.exportService
+ // Added filter by environment name
+
+ List<String> matrixServicesNames = scriptContext.getServiceList();
+
+ try {
+ new ServiceMarshalManager(staticServiceManager, scriptContext.getDictionaryManager()).saveServices(
+ scriptContext
+ .getEnvironmentManager()
+ .getServiceStorage()
+ .getServiceDescriptions()
+ .stream()
+ .filter(desc -> matrixServicesNames.contains(desc.getServiceName().toString()))
+ .collect(Collectors.toList()),
+ scriptContext
+ .getWorkspaceDispatcher().
+ createFolder(FolderType.REPORT, reportFolder, "services").toPath());
+ } catch (WorkspaceStructureException e) {
+ throw new EPSCommonException("Can`t create scripts folder", e);
+ }
+ }
+
+ @Override
+ public void addAlerts(Collection<AggregateAlert> aggregatedAlerts) {
+ }
+
+ @Override
+ public void closeReport() {
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void createTestCase(String reference, String description, int order, int matrixOrder, String tcId, int tcHash, AMLBlockType type, Set<String> tags) {
+ }
+
+ @Override
+ public void closeTestCase(StatusDescription status) {
+ }
+
+ @Override
+ public boolean isActionCreated() throws UnsupportedOperationException {
+ return false;
+ }
+
+ @Override
+ public void createAction(String id, String serviceName, String name, String messageType, String description, IMessage parameters, CheckPoint checkPoint, String tag, int hash, List<String> verificationsOrder, String outcome) {
+ }
+
+ @Override
+ public void closeAction(StatusDescription status, Object actionResult) {
+ }
+
+ @Override
+ public void openGroup(String name, String description) {
+ }
+
+ @Override
+ public void closeGroup(StatusDescription status) {
+ }
+
+ @Override
+ public void createVerification(String name, String description, StatusDescription status, ComparisonResult result) {
+ }
+
+ @Override
+ public void createMessage(MessageLevel level, String... messages) {
+ }
+
+ @Override
+ public void createMessage(MessageLevel level, Throwable e, String... messages) {
+ }
+
+ @Override
+ public void createException(Throwable cause) {
+ }
+
+ @Override
+ public void createTable(ReportTable table) {
+ }
+
+ @Override
+ public void createLogTable(List<String> header, List<LoggerRow> rows) {
+ }
+
+ @Override
+ public void setOutcomes(OutcomeCollector outcomes) {
+ }
+
+ @Override
+ public void createLinkToReport(String linkToReport) {
+ }
+
+ @Override
+ public IReportStats getReportStats() {
+ return null;
+ }
+}

diff:
@@ -21,6 +21,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -59,6 +60,7 @@ import com.exactpro.sf.storage.util.ServiceStorageHelper;
public class ServiceMarshalManager {
private static final Logger logger = LoggerFactory.getLogger(ServiceMarshalManager.class);
+ private static final String servicesFileExpression = ".xml";
private final DocumentBuilder domBuilder;
private final XPathExpression typeExpression;
@@ -77,6 +79,26 @@ public class ServiceMarshalManager {
}
}
+ public void saveServices(List<ServiceDescription> descriptions, Path folderPath) {
+ logger.debug("saveServices: begin");
+
+ for (ServiceDescription descr : descriptions) {
+ try {
+ Marshaller m = createMarshaller(ServiceDescription.class, DisabledServiceSettings.class, getServiceSettingsClass(descr.getType()));
+ File servicesFile = folderPath.resolve(descr.getName() + servicesFileExpression).toFile();
+ if (servicesFile.createNewFile()) {
+ m.marshal(descr, servicesFile);
+ } else {
+ logger.error("Could not create file {}", servicesFile.toPath());
+ }
+ } catch (JAXBException | IOException | SailfishURIException | ClassNotFoundException e) {
+ logger.error("Could not marshal service description {}", descr.getName(), e);
+ }
+ }
+
+ logger.debug("saveServices: end");
+ }
+
public Map<String, File> exportServices(List<ServiceDescription> descriptions) {
logger.info("exportServices: begin");

diff:
@@ -271,7 +271,7 @@ public class DefaultTestScriptStorage implements ITestScriptStorage {
try (ZipFile zipFile = new ZipFile(reportFile)) {
ZipEntry zipJson = zipFile.getEntry(FilenameUtils.removeExtension(reportFile.getName()) + "/" + ROOT_JSON_REPORT_FILE);
try (InputStream stream = zipFile.getInputStream(zipJson)) {
- return convertToTestScriptDescription(relativeReportPath.getParent().toString(), jsonObjectMapper.readValue(stream, ReportRoot.class));
+ return convertToTestScriptDescription(relativeReportPath.toString(), jsonObjectMapper.readValue(stream, ReportRoot.class));
}
}
}

original_message:
[RM] Feature #59800. Pack services settings which used in matrix into report archive.

Change-Id: I6169a7ad141c478f8f13c83490103f7cbd704252