Code Monkey home page Code Monkey logo

issuestant's People

Contributors

rexim avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

issuestant's Issues

Implement EventsSource class

Split from #20


Pieces

  • #43 GitHub events as a lazy stream
  • #47 Implement EventsSource.pollingIteration
  • #51 Unit Tests for EtagPolling
  • #48 Implement EventsSource.extractEvents
  • #46 Get rid of wart suppress

Build split forest from the actual GitHub's data

  • Split from #18
  • Depends on #14

Description

Let's use https://github.com/tsoding/Issuestant as the source of data. Only this repo should available for building a split forest from for now.

  • Implement /api/splitforest/<usr-or-org>/<repo> API call (only /api/splitforest/tsoding/issuestant should work for now)
  • /api/splitforest/tsoding/issuestant should return serialized Split Forest for issues of https://github.com/tsoding/Issuestant. To achive that just serialize list of SplitIssueTree with circe (we already depend on it)

Malformed request body on application start

Steps to reproduce

  • $ sbt run

Observed

An exception

org.http4s.MalformedMessageBodyFailure: Malformed request body: Invalid JSON
	at org.http4s.jawn.JawnInstances$$anonfun$jawnDecoder$1$$anonfun$apply$1.applyOrElse(JawnInstances.scala:16) ~[http4s-jawn_2.11-0.15.6.jar:0.15.6]
	at org.http4s.jawn.JawnInstances$$anonfun$jawnDecoder$1$$anonfun$apply$1.applyOrElse(JawnInstances.scala:14) ~[http4s-jawn_2.11-0.15.6.jar:0.15.6]
	at scala.PartialFunction$Lifted.apply(PartialFunction.scala:223) ~[scala-library-2.11.8.jar:1.0.0-M1]
	at scala.PartialFunction$Lifted.apply(PartialFunction.scala:219) ~[scala-library-2.11.8.jar:1.0.0-M1]
	at scalaz.stream.Process$$anonfun$partialAttempt$1.apply(Process.scala:392) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$partialAttempt$1.apply(Process.scala:392) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$attempt$1$$anonfun$apply$22.apply(Process.scala:230) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$attempt$1$$anonfun$apply$22.apply(Process.scala:230) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$attempt$1.apply(Process.scala:230) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$attempt$1.apply(Process.scala:229) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12$$anonfun$apply$13.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12$$anonfun$apply$13.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.Trampoline$$anonfun$delay$1.apply(Free.scala:262) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Trampoline$$anonfun$delay$1.apply(Free.scala:262) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go2$1(Free.scala:134) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go(Free.scala:137) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.run(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.go$1(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.step(Process.scala:97) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$Append.step(Process.scala:641) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$pipe$1.apply(Process.scala:143) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$pipe$1.apply(Process.scala:139) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$flatMap$1.apply(Process.scala:46) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$flatMap$1.apply(Process.scala:46) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.flatMap(Process.scala:46) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$Emit.flatMap(Process.scala:592) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$flatMap$4.apply(Process.scala:48) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$flatMap$4.apply(Process.scala:48) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.Free$$anonfun$map$1.apply(Free.scala:53) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$map$1.apply(Free.scala:53) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.scalaz$Free$$fastFlatMap(Free.scala:72) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$scalaz$Free$$fastFlatMap$1.apply(Free.scala:73) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$scalaz$Free$$fastFlatMap$1.apply(Free.scala:73) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.std.FunctionInstances$$anon$1$$anonfun$map$1.apply(Function.scala:56) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go2$1(Free.scala:134) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go(Free.scala:137) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.run(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.go$1(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.step(Process.scala:97) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$Append.step(Process.scala:641) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.go$3(Process.scala:482) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$3$3.apply(Process.scala:490) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$3$3.apply(Process.scala:490) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.concurrent.Task$$anonfun$flatMap$1$$anonfun$1.apply(Task.scala:36) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Task$$anonfun$flatMap$1$$anonfun$1.apply(Task.scala:36) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Task$.Try(Task.scala:403) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Task$$anonfun$flatMap$1.apply(Task.scala:36) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Task$$anonfun$flatMap$1.apply(Task.scala:34) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scala.Function1$$anonfun$andThen$1.apply(Function1.scala:52) ~[scala-library-2.11.8.jar:1.0.0-M1]
	at scala.Function1$$anonfun$andThen$1.apply(Function1.scala:52) ~[scala-library-2.11.8.jar:1.0.0-M1]
	at scalaz.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:58) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future$$anonfun$flatMap$1.apply(Future.scala:58) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future.step(Future.scala:109) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future.listen(Future.scala:75) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future$$anonfun$listen$1$$anonfun$apply$4.apply(Future.scala:79) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future$$anonfun$listen$1$$anonfun$apply$4.apply(Future.scala:79) ~[scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$map$1.apply(Free.scala:53) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$map$1.apply(Free.scala:53) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.scalaz$Free$$fastFlatMap(Free.scala:72) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$resume$1.apply(Free.scala:88) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$resume$1.apply(Free.scala:88) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.std.FunctionInstances$$anon$1$$anonfun$map$1.apply(Function.scala:56) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go2$1(Free.scala:134) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go(Free.scala:137) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.run(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future$$anonfun$apply$15$$anon$4.call(Future.scala:380) [scalaz-concurrent_2.11-7.1.11.jar:na]
	at scalaz.concurrent.Future$$anonfun$apply$15$$anon$4.call(Future.scala:380) [scalaz-concurrent_2.11-7.1.11.jar:na]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_76]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_76]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_76]
	at java.lang.Thread.run(Thread.java:745) [na:1.8.0_76]
Caused by: jawn.ParseException: exhausted input
	at jawn.AsyncParser.churn(AsyncParser.scala:234) ~[jawn-parser_2.11-0.10.4.jar:0.10.4]
	at jawn.AsyncParser.finish(AsyncParser.scala:98) ~[jawn-parser_2.11-0.10.4.jar:0.10.4]
	at jawnstreamz.package$$anonfun$parseJson$1$$anonfun$apply$2.apply(package.scala:31) ~[jawn-streamz_2.11-0.10.1.jar:0.10.1]
	at jawnstreamz.package$$anonfun$parseJson$1$$anonfun$apply$2.apply(package.scala:31) ~[jawn-streamz_2.11-0.10.1.jar:0.10.1]
	at scalaz.stream.Process$$anonfun$onComplete$1.apply(Process.scala:338) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$onComplete$1.apply(Process.scala:338) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12$$anonfun$apply$13.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12$$anonfun$apply$13.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.Trampoline$$anonfun$delay$1.apply(Free.scala:262) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Trampoline$$anonfun$delay$1.apply(Free.scala:262) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free$$anonfun$run$1.apply(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go2$1(Free.scala:134) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.go(Free.scala:137) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Free.run(Free.scala:187) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.go$1(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.step(Process.scala:97) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$Append.step(Process.scala:641) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.disconnect(Process.scala:247) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$Append.disconnect(Process.scala:641) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$disconnect$2.apply(Process.scala:249) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$disconnect$2.apply(Process.scala:249) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$suspend$1.apply(Process.scala:1498) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$suspend$1.apply(Process.scala:1497) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$go$1$1.apply(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.go$1(Process.scala:84) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$class.step(Process.scala:97) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$Append.step(Process.scala:641) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$suspendStep$1.apply(Process.scala:106) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$suspendStep$1.apply(Process.scala:105) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12$$anonfun$apply$13.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12$$anonfun$apply$13.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Util$.Try(Util.scala:38) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.stream.Process$$anonfun$2$$anonfun$apply$12.apply(Process.scala:115) ~[scalaz-stream_2.11-0.8.6.jar:na]
	at scalaz.Trampoline$$anonfun$delay$1.apply(Free.scala:262) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.Trampoline$$anonfun$delay$1.apply(Free.scala:262) ~[scalaz-core_2.11-7.1.11.jar:na]
	at scalaz.std.FunctionInstances$$anon$1$$anonfun$map$1.apply(Function.scala:56) ~[scalaz-core_2.11-7.1.11.jar:na]
	... 70 common frames omitted

Expected

No exceptions

Keep Alive while Etag polling

I think we close the connection on each iteration of etag polling. But I'm not sure about that. We have to investigate that.

Cover Polling with Unit Tests

Split from #20
TODO 19e120b5-c7a8-426e-b54c-170cf1d7d3da


It's actually not a trivial task, probably requires some decomposition. That's why it's a separate issues.

Initial implementation of REST API

Split from #18


Description

Just implement initial architecture for REST API.

  • REST API entry point should be https://<issuestant-site>/api/*,
  • HTTP implementation http4s
  • Communication format is JSON,
  • Implement /api/hello that answers something like { "message": "hello" } in application/json
  • Establish the requirement to document every API call and enforce it in every PR,
  • Consider using swagger for documenting REST API if possible with http4s,

Display split forest on Issuestant's web UI

Split form #18
Depends on #22


Description

  • Frontend infrastructure (Language, framework, etc)
  • When I open http:///tsoding/Issuestant I should be able to see the split tree which is readable
  • The data for the rendered split tree should be taken from the /api/splitforest/tsoding/issuestant REST API call.

Project bootstraping protocol

Description

Consider the following use case.

  • I create a bootstrap issue for a newly created project (similar to this morganey-lang/morganey-mode#1)
  • I enlist all of the things I want in the description.
  • I mention @Issuestant in the bootstrap issue.
  • @Issuestant recognizes the type of the project and the enlisted stuff (from existing presets probably)
  • @Issuestant bootstraps the project via series of issues and PRs. For example:
    • @Issuestant setups Travis CI
    • @Issuestant files an issue "Please enable this project in Travis CI" and assigns it to the Project Leader
    • After the Project Leader resolves the issue @Issuestant submits a PR with a predefined .travis.yml
  • @Issuestant uses a similar protocol for the rest of the enlisted items until the project is completely bootstrapped
  • If something goes wrong during the protocol @Issuestant mentions that immediately in a correspoding place and suggest possible ways of resolving a problem.
  • Such protocols can be configurable.

Logging is too cluttering

Right now on each HTTP request during ETAG polling we get the following bunch of lines.

13:30:40.030 [pool-8-thread-3] DEBUG org.http4s.blaze.pipeline.Stage - Beginning request: GET https://api.github.com/repos/tsoding/issuestant-playground/issues/events
13:30:40.264 [anInnocuousThread] DEBUG org.http4s.blaze.pipeline.Stage - SSL Read Request Status: Status = OK HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 862 bytesProduced = 833, java.nio.HeapByteBuffer[pos=833 lim=16921 cap=16921]
13:30:40.264 [anInnocuousThread] DEBUG org.http4s.blaze.pipeline.Stage - SSL Read Request Status: Status = BUFFER_UNDERFLOW HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 0 bytesProduced = 0, java.nio.HeapByteBuffer[pos=0 lim=16921 cap=16921]
13:30:40.264 [anInnocuousThread] DEBUG org.http4s.blaze.pipeline.Stage - SSL Read Request Status: Status = BUFFER_UNDERFLOW HandshakeStatus = NOT_HANDSHAKING
bytesConsumed = 0 bytesProduced = 0, java.nio.HeapByteBuffer[pos=0 lim=16921 cap=16921]
13:30:40.265 [http4s-blaze-client-5] DEBUG org.http4s.blaze.pipeline.Stage - Resetting org.http4s.client.blaze.Http1Connection after completing request.
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.blaze.pipeline.Stage - ClientTimeoutStage shutting down at Sat Aug 05 13:30:40 NOVT 2017
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.client.PoolManager - Recycling connection: allocated=1 idleQueue.size=0 waitQueue.size=0
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.client.PoolManager - Returning idle connection to pool: allocated=1 idleQueue.size=0 waitQueue.size=0
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.client.PoolManager - Requesting connection: allocated=1 idleQueue.size=1 waitQueue.size=0
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.client.PoolManager - Recycling connection: allocated=1 idleQueue.size=0 waitQueue.size=0
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.blaze.pipeline.Stage - ClientTimeoutStage starting up at Sat Aug 05 13:30:40 NOVT 2017
13:30:40.265 [pool-8-thread-4] DEBUG org.http4s.blaze.pipeline.Stage - Connection was idle. Running.

The only useful information it tells is that there was an HTTP request to https://api.github.com/repos/tsoding/issuestant-playground/issues/events. That's it.

I'm not really interested in each an individual HTTP request. I'm more interested in already happend GitHub events. Let's config logback to log only that.

Setup Wartremover

Description

Wartremover looks like a nice tool. Let's try it out! It should perform the check on CI, of course. I think while the project is small it should be relatively easy to enforce any kind of linting and static checks.

Heroku deploy

Split from #1

Parts

  • Implement the stage command (#11)
  • Setup auto deploy on PR merge (#12)

Add automatic copyright management

Issuestant should automatically (or with configuration) detect where the project store the copyright year, and should be able to update copyright after any commit is made in the next year.

Permalinking

Description

@Issuestant should automatically replace all of the dynamic src-links with the permanent ones with the current latest commit hash. For example:

https://github.com/tsoding/Issuestant/blob/master/src/main/scala/me/rexim/Mixer.scala

should be replaced with

https://github.com/tsoding/Issuestant/blob/<latest-commit-hash>/src/main/scala/me/rexim/Mixer.scala

That feature will require to give @Issuestant enough permissions to edit all of the comments and descriptions in a project. If after enabling that feature (somehow, we need to come up with a specific mechanism for enabling features, maybe via filing an issue) @Issuestant doesn't have enough permissions, he should file an issue about that and assign it to the Project Leader for resolution.


Pieces

  • #38 Implement EventsSource class
  • #37 Implement Permalink class

CI cache

The build is slow because of the lack of one

GitHub events as a lazy stream

Split from #38
Introduced in #41
TODO 5af3a001-e611-42a2-9cbf-b7f64780fe76

Implement a lazy stream of GitHub events.All of the polling, retries and error handling should be incorporated into that stream.

Build split issue tree

This issue has been split in #21

Pieces

  • #14 Initial implementation of REST API
  • #22 Build split forest from the actual GitHub's data
  • #23 Display split forest on Issuestant's web UI

Description

I should be able somehow to tell (hardcode is ok at the moment) to Issuestant to gather all of the issues from a project, analyse their split links, build a split tree and visualize it on the Issuestant's web UI. Manual request to update the tree is ok at the moment.

Split Tree

Split tree is a tree where each node is an issue. Issues that contain Split from # in there description are considered to be children of issue . It's more correct to call that "Split forest", because there is no single root. But creating artificial root is ok. Could be just the name of the project.

If issue ForNeVeR/Jabber-Net#2 contains Split from #1 in the description, the split tree will look like:

ForNeVeR/Jabber-Net#1
|\
| ForNeVeR/Jabber-Net#2
.
.

Setup initial infrastructure

ANY DEVELOPMENT ON THIS PROJECT IS FORBIDDEN UNTIL THIS ISSUE IS RESOLVED

Parts

  • Proper issue labels
  • Project skeleton (#2)
  • Travis CI (#3)
  • Coveralls code coverage (#4)
  • Heroku deploy (#5)

Heartbeat

Description

Heroku shuts down unused instances. We can organize some kind of heartbeat from my personal VPS (which I don't wanna use for hosting because of the limited resources) to wake Issuestant up until we have enough resources for a better hosting.

Idea for the Github Activity de-duplication based on timestamps

Here is what I was thinking of during the stream. I made events an infinite list that follows a precise pattern (with delay being the shift in time where the pattern repeats.)

module Main where

type Timestamp = Int

type Event = (Timestamp,String)

type Response = (Timestamp,[Event])

-- with delay >7 the next iteration of responses is completely disjoint
-- with 1..6 part of the new cascade overlaps and only a tail is actually new

delay = 10

events :: [Response]
events = [
    (0, [ (1,"A"),(2,"B"),(3,"C")                         ]),
    (4, [ (1,"A"),(2,"B"),(3,"C"),(4,"D")                 ]),
    (5, [                 (3,"C"),(4,"D")                 ]),
    (6, [                 (3,"C"),(4,"D"),(6,"E")         ]),
    (7, [                         (4,"D"),(6,"E"),(7,"F") ])
  ] ++
    map (\(x,y) -> (x+delay, map (\(z,w) -> (z+delay,w)) y)) events

main = print $ take 20 filteredEvents

filteredEvents = concat $ map snd $ scanl collect (0, []) events

-- to see how this data structure is similar to yours you can try instead:
-- filteredEvents = scanl collect (0, []) events

collect :: (Timestamp, [Event]) -> Response -> (Timestamp, [Event])
collect (min_t, _) (t, es)
  | length news > 0 = (new_t, news)
  | otherwise       = (min_t, []  )
  where
    news = filter (\(t, _) -> t > min_t) es
    new_t = maximum . map fst $ news -- protected by the guard length news > 0

Examples:

-- delay = 10
[(1,"A"),(2,"B"),(3,"C"),(4,"D"),(6,"E"),(7,"F"),(11,"A"),(12,"B"),(13,"C"),(14,"D"),(16,"E"),(17,"F"),(21,"A"),(22,"B"),(23,"C"),(24,"D"),(26,"E"),(27,"F"),(31,"A"),(32,"B")]
-- delay = 1
[(1,"A"),(2,"B"),(3,"C"),(4,"D"),(6,"E"),(7,"F"),(8,"F"),(9,"F"),(10,"F"),(11,"F"),(12,"F"),(13,"F"),(14,"F"),(15,"F"),(16,"F"),(17,"F"),(18,"F"),(19,"F"),(20,"F"),(21,"F")]
-- delay = 5
[(1,"A"),(2,"B"),(3,"C"),(4,"D"),(6,"E"),(7,"F"),(8,"C"),(9,"D"),(11,"E"),(12,"F"),(13,"C"),(14,"D"),(16,"E"),(17,"F"),(18,"C"),(19,"D"),(21,"E"),(22,"F"),(23,"C"),(24,"D")]
-- delay = 6
[(1,"A"),(2,"B"),(3,"C"),(4,"D"),(6,"E"),(7,"F"),(8,"B"),(9,"C"),(10,"D"),(12,"E"),(13,"F"),(14,"B"),(15,"C"),(16,"D"),(18,"E"),(19,"F"),(20,"B"),(21,"C"),(22,"D"),(24,"E")]

I also wonder if it can be improved / simplified further.

Update: oops I was shadowing Prelude.last

Update 2: another idea, in case one doesn't want to lose the information about the grouping of events from the api, instead of concat:

-- delay = 6
-- filteredEvents = filter (not . null) $ map snd $ scanl collect (0, []) events
[[(1,"A"),(2,"B"),(3,"C")],[(4,"D")],[(6,"E")],[(7,"F")],[(8,"B"),(9,"C")],[(10,"D")],[(12,"E")],[(13,"F")],[(14,"B"),(15,"C")],[(16,"D")],[(18,"E")],[(19,"F")],[(20,"B"),(21,"C")],[(22,"D")],[(24,"E")],[(25,"F")],[(26,"B"),(27,"C")],[(28,"D")],[(30,"E")],[(31,"F")]]

Permalink Protocol FSM

Split from #20
Introduced in #41
TODO(#42)

May supersede #37

The Permalink service should be essentially an FSM. The full set of the possible states of such FSM should be represented by ADT. For example

  sealed trait PermalinkProtocol {
    def handleEvent(event: Event): PermalinkProtocol
  }
  case class PermalinkServingState(..) extends PermalinkProtocol
  case class PernalinkAskPermissionState(..) extends PermalinkProtocol

Automatic version management

Issuestant should be able to increment project version. I imagine two modes:

  • manual increment mode: administrator choose the desired version, and Issuestant updates the version everywhere in the project (common places are various pom.xml, build.gradle, AssemblyInfo.*, package.json; probably appveyor.yml)
  • automatic increment mode: Issuestant should consider the issues that were closed from the time of the last release and increments the version accordingly to semver specification (e.g. when some features with tag semver:major were implemented, it should bump major version number) (I think Node.js guys have automation and GitHub tag layout for this task; check it if interested)

Implement Permalink class

Split from #20
TODO(#37)


Don't forget to introduce dependencies that enable Permalink with modifying comments, modifying issue descriptions and fileing issues.

The Permalink service should be essentially an FSM. The full set of the possible states of such FSM should be represented by ADT. For example

  sealed trait PermalinkProtocol {
    def handleEvent(event: Event): PermalinkProtocol
  }
  case class PermalinkServingState(..) extends PermalinkProtocol
  case class PernalinkAskPermissionState(..) extends PermalinkProtocol

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.