Skip to content

Commit

Permalink
The plugin is now implemented as a Service and it now loads any activ…
Browse files Browse the repository at this point in the history
…e entry on load-up.
  • Loading branch information
DevSlashRichie committed Feb 2, 2021
1 parent 4b059ff commit e9b0aae
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 36 deletions.
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {
}

group = "io.github.ricardormdev.clockifyplugin"
version = "1.0.0"
version = "1.0.1"

tasks.withType<JavaCompile> {
sourceCompatibility = "1.8"
Expand All @@ -36,6 +36,7 @@ dependencies {
implementation("com.auth0:java-jwt:3.12.0")

testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
testImplementation("io.mockk:mockk:1.10.5")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.3.1")
}

Expand Down
39 changes: 35 additions & 4 deletions src/main/kotlin/io/github/ricardormdev/clockifyplugin/Plugin.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package io.github.ricardormdev.clockifyplugin

import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.WindowManager
import io.github.ricardormdev.clockifyplugin.api.API
import io.github.ricardormdev.clockifyplugin.api.models.TimeEntry
import io.github.ricardormdev.clockifyplugin.api.websocket.ClockifyEvent
import io.github.ricardormdev.clockifyplugin.api.websocket.WebSocketClient
import io.github.ricardormdev.clockifyplugin.api.websocket.authentication.AuthenticateTokens
import io.github.ricardormdev.clockifyplugin.api.websocket.authentication.User
import io.github.ricardormdev.clockifyplugin.notification.Notifier
import io.github.ricardormdev.clockifyplugin.settings.settingsState
import java.text.SimpleDateFormat
import java.time.Duration
import java.time.Instant
import java.util.*
import java.util.logging.ConsoleHandler
import java.util.logging.Level
import java.util.logging.Logger
import kotlin.collections.HashMap


class Plugin {
@Service
class Plugin : Disposable {

// LOAD OUR LOGGER
private var logger: Logger = Logger.getLogger("ClockifyPlugin")
Expand Down Expand Up @@ -133,6 +140,13 @@ class Plugin {
dataController.updateUser()
}

// Check if the user has any entry started
val activeEntry = api.getEntryInProgress(dataController.user.activeWorkspace, dataController.user.id)

activeEntry.run {
startLocalWork(this)
}

Notifier.notifyInfo("Clockify loaded correctly.")
}

Expand Down Expand Up @@ -171,9 +185,22 @@ class Plugin {
}
}

private fun startLocalWork() {
private fun startLocalWork(timeEntry: TimeEntry? = null) {
working = true
timer.startTimer()

if (timeEntry != null) {
val startDate = timeEntry.timeInterval?.start
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
format.timeZone = TimeZone.getTimeZone("UTC")

val asDate = format.parse(startDate)
val duration = Duration.between(asDate.toInstant(), Instant.now())

timer.startTimer(duration.seconds.toInt())
} else {
timer.startTimer()
}

}

private fun stopLocalWork() {
Expand All @@ -189,4 +216,8 @@ class Plugin {
else "You are not working."
}

override fun dispose() {
client.close()
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.github.ricardormdev.clockifyplugin

import com.intellij.openapi.application.PreloadingActivity
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.components.service
import com.intellij.openapi.progress.ProgressIndicator

class PluginLoader : PreloadingActivity() {

companion object {
// Our plugin instance
val plugin = Plugin()
val plugin: Plugin
get() = service()
}

override fun preload(indicator: ProgressIndicator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ class PluginTimer(private val plugin: Plugin) {
private lateinit var timer: Timer
private var seconds = 0

fun startTimer() {
seconds = 0
/**
* Start the counter of the plugin.
* @param startSeconds Set an already counting seconds. Default 0
*/
fun startTimer(startSeconds: Int = 0) {
seconds = startSeconds
timer = Timer()
val task = object : TimerTask() {
override fun run() {
Expand All @@ -19,11 +23,18 @@ class PluginTimer(private val plugin: Plugin) {
timer.schedule(task, 0, 1000L)
}

/**
* Stop counting secconds, this method is just local.
*/
fun stopTimer() {
timer.cancel()
plugin.updateContainers()
}

/**
* Get how many seconds had been passed.
* @return The seconds as an Int
*/
fun getTiming() : Int {
return seconds
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ class API(auth: AuthenticateTokens) {
return request("workspaces/$workspaceId/tags", Array<Tag>::class.java, arrayOf())
}

fun getEntryInProgress(workspaceId: String, userId: String) : TimeEntry? {
val entries = request("/v1/workspaces/${workspaceId}/user/${userId}/time-entries?in-progress=true&hydrated=true",
Array<TimeEntry>::class.java, arrayOf())
if (entries.isNotEmpty()) {
return entries[0]
}
return null
}

fun setDefaultWorkspace(workspaceId: String, userId: String) {

val url = url("users/$userId/defaultWorkspace/$workspaceId")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.codec.http.EmptyHttpHeaders
import io.netty.handler.codec.http.HttpClientCodec
import io.netty.handler.codec.http.HttpHeaders
import io.netty.handler.codec.http.HttpObjectAggregator
Expand Down Expand Up @@ -33,7 +34,7 @@ class WebSocketClient(uri: String) {

val handler = WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, false, HttpHeaders.EMPTY_HEADERS,1280000
uri, WebSocketVersion.V13, null, false, EmptyHttpHeaders.INSTANCE,1280000
), adapters
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.util.IconLoader
import com.intellij.openapi.wm.WindowManager
import io.github.ricardormdev.clockifyplugin.PluginLoader
import io.github.ricardormdev.clockifyplugin.settings.AppSettingsConfigurable

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AppSettingsComponent {
private val password = JBPasswordField()

private val mainPanel: JPanel = FormBuilder.createFormBuilder()
.addLabeledComponent(JBLabel("Username & Email: "), email, 1, false)
.addLabeledComponent(JBLabel("Username or Email: "), email, 1, false)
.addLabeledComponent(JBLabel("Password: "), password, 1, false)
.addComponentFillVertically(JPanel(), 0)
.panel
Expand Down
38 changes: 31 additions & 7 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,64 @@
<name>Clockify</name>

<!-- Indicate this plugin can be loaded in all IntelliJ Platform-based products. -->
<depends>com.intellij.modules.all</depends>
<depends>com.intellij.modules.lang</depends>

<!-- Text to display as description on Preferences/Settings | Plugin page -->
<description>
<![CDATA[
Track your working time from JetBrains on Clockify!
<br />
<b> Features: </b>
<ul>
<li> Start & Stop your time directly on the IDE. </li>
<li> Track your working time. </li>
<li> Select and define session description. </li>
</ul>
<br />
<b> Getting Started: </b>
<br />
Go into <i> Other Settings > Clockify </i>, then write your Clockify login details.
<br />
Start tracking your time by clicking on the low-right corner (on the status bar) and click again to stop tracking.
<br />
<img src="https://i.imgur.com/4cRCHEr.png" alt="Clockify at status bar" /><br />
<img src="https://i.imgur.com/tmq3GcF.png" alt="Tracking time" /><br />
<img src="https://i.imgur.com/JYdWs5R.png" alt="Stop tracking your time" /><br />
<br />
Assign session descriptions when starting to track your time.
<br />
<img src="https://i.imgur.com/xKHH0c2.png" alt="Add your session description">
]]>
</description>
<change-notes>
<![CDATA[
<ul>
<li><b>1.0</b> Release 1.0.0</li>
<li><b>1.0.1</b> It now loads any active entry on loading the IDE.</li>
<li><b>1.0</b> Initial Release 1.0.0</li>
</ul>
]]>
</change-notes>

<!-- Text to display as company information on Preferences/Settings | Plugin page -->
<vendor url="https://ricardormdev.github.io">Ricardo Rodriguez Medina</vendor>
<vendor url="https://ricardormdev.github.io" email="romr020606@gmail.com">Ricardo Rodriguez Medina</vendor>

<idea-version since-build="201" />
<idea-version since-build="201.6668.113" />

<extensions defaultExtensionNs="com.intellij">

<preloadingActivity implementation="io.github.ricardormdev.clockifyplugin.PluginLoader" />
<postStartupActivity implementation="io.github.ricardormdev.clockifyplugin.PluginFactory" />
<applicationService serviceImplementation="io.github.ricardormdev.clockifyplugin.settings.ApplicationSettingsState" />
<statusBarWidgetFactory implementation="io.github.ricardormdev.clockifyplugin.widget.WidgetFactory" />
<applicationConfigurable
parentId="tools"
groupId="other"
groupWeight="200"
instance="io.github.ricardormdev.clockifyplugin.settings.AppSettingsConfigurable"
id="io.github.ricardormdev.clockifyplugin.settings.AppSettingsConfigurable"
displayName="Clockify Plugin">
displayName="Clockify">
</applicationConfigurable>

<applicationService serviceImplementation="io.github.ricardormdev.clockifyplugin.Plugin" />

</extensions>


Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,98 @@
package io.github.ricardormdev.clockifyplugin

import io.github.ricardormdev.clockifyplugin.api.API
import io.github.ricardormdev.clockifyplugin.api.models.Project
import io.github.ricardormdev.clockifyplugin.api.models.Tag
import io.github.ricardormdev.clockifyplugin.api.models.UserInterface
import io.github.ricardormdev.clockifyplugin.api.models.Workspace
import io.github.ricardormdev.clockifyplugin.api.websocket.authentication.AuthenticateTokens
import io.github.ricardormdev.clockifyplugin.api.websocket.authentication.UserLogin
import io.github.ricardormdev.clockifyplugin.settings.ApplicationSettingsState
import io.github.ricardormdev.clockifyplugin.settings.settingsState
import io.mockk.*
import org.junit.Before
import org.junit.Test
import kotlin.test.assertTrue

class RepositoryTest {

@Test
fun testLogin() {
fun testWorking() {
val plugin = buildPlugin()

assertTrue(plugin.getStatusMessage().contains("You are not working."), "The message should mention is not working")
plugin.startWorking("workspaceA", "a", true, "Random Description")
assertTrue(plugin.logged, "The user should be logged.")
assertTrue(plugin.working, "The status should be working.")
val timer : PluginTimer = getField("timer", plugin)
assertTrue(plugin.getStatusMessage().contains("You've worked for:"), "The message should mention is working")
Thread.sleep(1000)
assertTrue(timer.getTiming() > 0, "The timer should be started.")
}

fun buildPlugin() : Plugin {
val plugin = Plugin()
val auther = mockAuthenticator()
val controller = mockController()
val api = mockAPI()

assignVarToObj("user", controller, api.getUser("user"))
assignVarToObj("dataController", plugin, controller)
assignVarToObj("api", plugin, api)
assignVarToObj("timer", plugin, PluginTimer(plugin))
assignVarToObj("authenticator", plugin, auther)
plugin.logged = true

return plugin
}

@Test
fun testWorking() {
val plugin = Plugin()
val timer = PluginTimer(plugin)
fun assignVarToObj(name: String, target: Any, value: Any) {
val field = target::class.java.getDeclaredField(name)
field.isAccessible = true
field.set(target, value)
}

@Suppress("UNCHECKED_CAST")
fun <T> getField(fieldName: String, target: Any) : T {
val field = target::class.java.getDeclaredField(fieldName)
field.isAccessible = true
return field.get(target) as T
}

fun mockController() : PluginDataController {
return PluginDataController(mockAPI())
}

fun mockAuthenticator(): AuthenticateTokens {
return mockkClass(AuthenticateTokens::class) {
every { retrieveFullUser() } returns UserLogin("user", "email@domain.com", "UserName", "token", "refreshToken", arrayOf())
}
}

fun mockAPI(): API {
return mockkClass(API::class) {
every { getWorkspaces() } returns arrayOf(
Workspace("workspaceA", "workspaceA"),
Workspace("workspaceB", "workspaceB")
)

every { getProjects("workspaceA") } returns
arrayOf(Project("a", "Project A"), Project("b", "Project B"), Project("b", "Project B"))

every { getProjects("workspaceB") } returns arrayOf(
Project("a", "Project A"),
Project("b", "Project B"),
Project("b", "Project B"))

every { getUser("user") } returns
UserInterface("user", "UserName", "workspaceA")

every { getTags(any()) } returns
arrayOf(Tag("TagA", "TagNameA"), Tag("TagB", "TagNameB"))


timer.startTimer()
println(timer.getTiming())
assert(timer.getTiming() > 0)
every { startWorking(any(), any(), any(), any()) } returns Unit
}
}

}

0 comments on commit e9b0aae

Please sign in to comment.