Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# CodeClocker Changelog

## 1.0.2
## 1.0.4 - 2025-04-03
### Added
- Validate API key input
- Improve onboarding UX

## 1.0.2 - 2025-04-01
### Added
- Support IntelliJ Platform 2024.3.5
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

## What's CodeClocker

CodeClocker automatically tracks coding activity and visualizes it on web dashboard.
CodeClocker automatically tracks coding activity and visualizes it on [web dashboard on CodeClocker Hub](https://hub.codeclocker.com/).

![Demo](https://raw.githubusercontent.com/codeclocker/codeclocker-intellij-plugin/main/docs/img/demo.gif)

Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html

pluginGroup = com.codeclocker
pluginName = CodeClocker - IntelliJ IDEA Activity Tracker
pluginName = CodeClocker
pluginRepositoryUrl = https://github.com/codeclocker/codeclocker-intellij-plugin
# SemVer format -> https://semver.org
pluginVersion = 1.0.2
pluginVersion = 1.0.4

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 242
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import static java.awt.AWTEvent.FOCUS_EVENT_MASK;

import com.codeclocker.plugin.intellij.apikey.ApiKeyActivationCheckerTask;
import com.codeclocker.plugin.intellij.apikey.ApiKeyPromptStartupActivity;
import com.codeclocker.plugin.intellij.listeners.FocusListener;
import com.codeclocker.plugin.intellij.reporting.DataReportingTask;
import com.codeclocker.plugin.intellij.subscription.SubscriptionStateCheckerTask;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.ProjectActivity;
Expand Down Expand Up @@ -51,6 +51,6 @@ private static void startDataReportingTask() {
}

private static void startCheckingApiKeyStatus() {
ApplicationManager.getApplication().getService(ApiKeyActivationCheckerTask.class).schedule();
ApplicationManager.getApplication().getService(SubscriptionStateCheckerTask.class).schedule();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.codeclocker.plugin.intellij.apikey;

import com.intellij.openapi.ui.InputValidator;
import com.intellij.openapi.util.NlsSafe;

public class ApiKeyInputValidator implements InputValidator {

@Override
public boolean checkInput(@NlsSafe String s) {
return isValid(s);
}

@Override
public boolean canClose(@NlsSafe String s) {
return isValid(s);
}

private static boolean isValid(String apiKey) {
return apiKey != null && apiKey.startsWith("cc-") && apiKey.length() == 39;
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
package com.codeclocker.plugin.intellij.apikey;

import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;

public class ApiKeyPromptStartupActivity {

private static final String FIRST_LAUNCH_PROPERTY = "com.codeclocker.first-launch";

public static void showApiKeyDialog() {
PropertiesComponent properties = PropertiesComponent.getInstance();
boolean prompt = properties.getBoolean("com.codeclocker.prompt-for-api-key", true);

if (ApiKeyPersistence.getApiKey() == null && prompt) {
ApplicationManager.getApplication()
.invokeLater(
() -> {
EnterApiKeyAction.showAction();
properties.setValue(FIRST_LAUNCH_PROPERTY, false);
});
if (ApiKeyPersistence.getApiKey() == null) {
ApplicationManager.getApplication().invokeLater(EnterApiKeyAction::showAction);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.codeclocker.plugin.intellij.HubHost.HUB_UI_HOST;
import static org.apache.commons.lang3.StringUtils.isBlank;

import com.codeclocker.plugin.intellij.subscription.SubscriptionStateCheckerTask;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
Expand All @@ -23,14 +24,14 @@ public static void showAction() {
Messages.showYesNoCancelDialog(
text,
"Enter CodeClocker API Key",
"I Have API Key",
"Get API Key",
"I Have API Key",
"Cancel",
Messages.getInformationIcon());

if (result == Messages.YES) {
if (result == Messages.NO) {
showApiKeyInputDialog();
} else if (result == Messages.NO) {
} else if (result == Messages.YES) {
BrowserUtil.browse(HUB_UI_HOST + "/api-key");
showApiKeyInputDialog();
}
Expand All @@ -53,8 +54,12 @@ private static String getText() {
private static void showApiKeyInputDialog() {
String apiKey =
Messages.showInputDialog(
"Enter your API key:", "Activate CodeClocker", Messages.getInformationIcon());
"Enter your API key\n\nCopy from: hub.codeclocker.com/api-key",
"Activate CodeClocker",
Messages.getInformationIcon(),
null,
new ApiKeyInputValidator());
ApiKeyPersistence.persistApiKey(apiKey);
ApplicationManager.getApplication().getService(ApiKeyActivationCheckerTask.class).schedule();
ApplicationManager.getApplication().getService(SubscriptionStateCheckerTask.class).schedule();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
import static org.apache.commons.lang3.StringUtils.isBlank;

import com.codeclocker.plugin.intellij.apikey.ApiKeyLifecycle;
import com.codeclocker.plugin.intellij.apikey.CheckApiKeyStateHttpClient;
import com.codeclocker.plugin.intellij.config.ConfigProvider;
import com.codeclocker.plugin.intellij.services.ChangesActivityTracker;
import com.codeclocker.plugin.intellij.services.ChangesSample;
import com.codeclocker.plugin.intellij.services.TimeSpentActivityTracker;
import com.codeclocker.plugin.intellij.services.TimeSpentPerProjectSample;
import com.codeclocker.plugin.intellij.subscription.CheckSubscriptionStateHttpClient;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
Expand All @@ -28,7 +28,7 @@

public final class DataReportingTask implements Disposable {

private static final Logger LOG = Logger.getInstance(CheckApiKeyStateHttpClient.class);
private static final Logger LOG = Logger.getInstance(CheckSubscriptionStateHttpClient.class);

private final int flushToServerFrequencySeconds;
private final TimeSpentActivityTracker timeSpentActivityTracker;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.codeclocker.plugin.intellij.apikey;
package com.codeclocker.plugin.intellij.subscription;

import static com.codeclocker.plugin.intellij.HubHost.HUB_API_HOST;
import static com.codeclocker.plugin.intellij.Timeouts.CONNECT_TIMEOUT;
Expand All @@ -8,9 +8,9 @@
import com.intellij.util.io.HttpRequests;
import org.jetbrains.annotations.Nullable;

public class CheckApiKeyStateHttpClient {
public class CheckSubscriptionStateHttpClient {

private static final Logger LOG = Logger.getInstance(CheckApiKeyStateHttpClient.class);
private static final Logger LOG = Logger.getInstance(CheckSubscriptionStateHttpClient.class);

@Nullable
public String check(String apiKey) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
package com.codeclocker.plugin.intellij.apikey;
package com.codeclocker.plugin.intellij.subscription;

import static com.codeclocker.plugin.intellij.ScheduledExecutor.EXECUTOR;
import static com.codeclocker.plugin.intellij.apikey.ApiKeyLifecycle.continueCollectingActivityData;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.commons.lang3.StringUtils.isBlank;

import com.codeclocker.plugin.intellij.apikey.ApiKeyPersistence;
import com.codeclocker.plugin.intellij.config.ConfigProvider;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import java.util.concurrent.ScheduledFuture;

public class ApiKeyActivationCheckerTask implements Disposable {
public class SubscriptionStateCheckerTask implements Disposable {

private static final Logger LOG = Logger.getInstance(ApiKeyActivationCheckerTask.class);
private static final Logger LOG = Logger.getInstance(SubscriptionStateCheckerTask.class);

private final int checkApiKeyStatusFrequencySeconds;

private final CheckApiKeyStateHttpClient checkSubscriptionStateByApiKeyHttpClient;
private final CheckSubscriptionStateHttpClient checkSubscriptionStateByApiKeyHttpClient;
private ScheduledFuture<?> task;

public ApiKeyActivationCheckerTask() {
public SubscriptionStateCheckerTask() {
this.checkSubscriptionStateByApiKeyHttpClient =
ApplicationManager.getApplication().getService(CheckApiKeyStateHttpClient.class);
ApplicationManager.getApplication().getService(CheckSubscriptionStateHttpClient.class);
ConfigProvider configProvider =
ApplicationManager.getApplication().getService(ConfigProvider.class);
this.checkApiKeyStatusFrequencySeconds = configProvider.getCheckApiKeyStatusFrequencySeconds();
Expand All @@ -35,10 +36,7 @@ public void schedule() {

this.task =
EXECUTOR.scheduleWithFixedDelay(
this::checkApiKeyState,
checkApiKeyStatusFrequencySeconds,
checkApiKeyStatusFrequencySeconds,
SECONDS);
this::checkApiKeyState, 10, checkApiKeyStatusFrequencySeconds, SECONDS);
}

private void checkApiKeyState() {
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
serviceImplementation="com.codeclocker.plugin.intellij.services.TimeSpentActivityTracker"/>
<applicationService
serviceImplementation="com.codeclocker.plugin.intellij.services.ChangesActivityTracker"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.apikey.CheckApiKeyStateHttpClient"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.subscription.CheckSubscriptionStateHttpClient"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.reporting.ActivitySampleHttpClient"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.apikey.ApiKeyLifecycle"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.config.ConfigProvider"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.config.PluginConfigHttpClient"/>
<applicationService
serviceImplementation="com.codeclocker.plugin.intellij.apikey.ApiKeyActivationCheckerTask"/>
serviceImplementation="com.codeclocker.plugin.intellij.subscription.SubscriptionStateCheckerTask"/>

<postStartupActivity implementation="com.codeclocker.plugin.intellij.ListenerRegistrator"/>

Expand Down
Loading