Skip to content

Commit

Permalink
initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
deniswsrosa committed Apr 22, 2024
1 parent 87860ce commit 41ccad4
Show file tree
Hide file tree
Showing 25 changed files with 1,080 additions and 91 deletions.
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Couchbase Jetbrains Plugin
3# Couchbase Jetbrains Plugin

Welcome to the official Couchbase plugin for the Jetbrains Platform

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/couchbase/intellij/VirtualFileKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class VirtualFileKeys {
public static final Key<String> ID = new Key<>("ID");
public static final Key<String> CAS = new Key<>("cas");

public static final Key<String> SEARCH_INDEX = new Key<>("searchindex");

public static final Key<String> CBL_CON_ID = new Key<>("cbl_conn_id");

public static final Key<String> READ_ONLY = new Key<>("readonly");
Expand Down
31 changes: 27 additions & 4 deletions src/main/java/com/couchbase/intellij/database/ActiveCluster.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class ActiveCluster implements CouchbaseClusterEntity {
* Connection listeners are invoked only when a new cluster connection is established
*/
private final List<Runnable> newConnectionListener = new ArrayList<>();

private static List<String> searchNodes;
private Cluster cluster;
private SavedCluster savedCluster;
private String password;
Expand All @@ -65,10 +67,6 @@ public class ActiveCluster implements CouchbaseClusterEntity {
*/
private static final List<Consumer<ActiveCluster>> clusterListeners = new ArrayList<>();

/**
* Subscribers are invoked every time context is changed on this cluster
*/
private final List<Consumer<Optional<QueryContext>>> queryContextListeners = new ArrayList<>();
private Integer queryLimit = 200;

protected ActiveCluster() {
Expand All @@ -85,6 +83,10 @@ public static void subscribe(Consumer<ActiveCluster> listener) {
}
}

public static void subscribeNew(Consumer<ActiveCluster> listener) {
clusterListeners.add(listener);
}

public void setQueryContext(@Nullable QueryContext context) {
this.queryContext.set(context);
}
Expand All @@ -103,6 +105,10 @@ public void registerNewConnectionListener(Runnable runnable) {
this.newConnectionListener.add(runnable);
}

public void deregisterNewConnectionListener(Runnable runnable) {
this.newConnectionListener.remove(runnable);
}

public Cluster get() {
return cluster;
}
Expand Down Expand Up @@ -200,6 +206,14 @@ public void run(@NotNull ProgressIndicator indicator) {
setVersion(overview.getNodes().get(0).getVersion()
.substring(0, overview.getNodes().get(0).getVersion().indexOf('-')));

if (hasSearchService()) {
searchNodes = new ArrayList<>();
searchNodes.addAll(overview.getNodes().stream()
.filter(e -> e.getServices().contains("fts"))
.map(e -> e.getHostname().substring(0, e.getHostname().indexOf(":")))
.collect(Collectors.toSet()));
}

//Notify Listeners that we connected to a new cluster.
//NOTE: Only singletons can register here, otherwise we will get a memory leak
CompletableFuture.runAsync(() -> {
Expand Down Expand Up @@ -259,6 +273,7 @@ public void disconnect() {
this.buckets = null;
this.disconnectListener = null;
this.permissions = null;
this.searchNodes = null;
}

public String getUsername() {
Expand Down Expand Up @@ -455,6 +470,10 @@ public boolean hasQueryService() {
return CBConfigUtil.hasQueryService(services);
}

public boolean hasSearchService() {
return CBConfigUtil.hasSearchService(services);
}

public boolean isCapella() {
return savedCluster != null && savedCluster.getUrl().contains("cloud.couchbase.com");
}
Expand All @@ -473,4 +492,8 @@ public void setQueryLimit(Integer limit) {
public @Nullable Integer getQueryLimit() {
return this.queryLimit;
}

public static List<String> searchNodes() {
return searchNodes;
}
}
94 changes: 85 additions & 9 deletions src/main/java/com/couchbase/intellij/database/DataLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import com.couchbase.client.java.manager.collection.CollectionSpec;
import com.couchbase.client.java.manager.collection.ScopeSpec;
import com.couchbase.client.java.manager.query.QueryIndex;
import com.couchbase.client.java.manager.search.SearchIndex;
import com.couchbase.client.java.manager.search.SearchIndexManager;
import com.couchbase.intellij.VirtualFileKeys;
import com.couchbase.intellij.database.entity.CouchbaseCollection;
import com.couchbase.intellij.persistence.*;
Expand Down Expand Up @@ -51,6 +53,7 @@
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.couchbase.intellij.VirtualFileKeys.READ_ONLY;

Expand Down Expand Up @@ -119,7 +122,15 @@ public static void listScopes(DefaultMutableTreeNode parentNode, Tree tree) {
for (ScopeSpec scopeSpec : scopes) {
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(new ScopeNodeDescriptor(scopeSpec.name(), ActiveCluster.getInstance().getId(), bucketName));

childNode.add(new DefaultMutableTreeNode(new LoadingNodeDescriptor()));
if (ActiveCluster.getInstance().hasSearchService()) {
DefaultMutableTreeNode search = new DefaultMutableTreeNode(new SearchNodeDescriptor(scopeSpec.name(), bucketName));
search.add(new DefaultMutableTreeNode(new LoadingNodeDescriptor()));
childNode.add(search);
}

DefaultMutableTreeNode collections = new DefaultMutableTreeNode(new CollectionsNodeDescriptor(scopeSpec.name(), bucketName));
collections.add(new DefaultMutableTreeNode(new LoadingNodeDescriptor()));
childNode.add(collections);

parentNode.add(childNode);
}
Expand All @@ -144,23 +155,25 @@ public static void listScopes(DefaultMutableTreeNode parentNode, Tree tree) {

public static void listCollections(DefaultMutableTreeNode parentNode, Tree tree) {
Object userObject = parentNode.getUserObject();
if (userObject instanceof ScopeNodeDescriptor) {
if (userObject instanceof CollectionsNodeDescriptor) {
CompletableFuture.runAsync(() -> {
tree.setPaintBusy(true);
try {
parentNode.removeAllChildren();
ScopeNodeDescriptor scopeDesc = (ScopeNodeDescriptor) userObject;
Map<String, Integer> counts = CouchbaseRestAPI.getCollectionCounts(scopeDesc.getBucket(), scopeDesc.getText());
CollectionsNodeDescriptor colsDesc = (CollectionsNodeDescriptor) userObject;
Map<String, Integer> counts = CouchbaseRestAPI.getCollectionCounts(colsDesc.getBucket(), colsDesc.getScope());

List<CollectionSpec> collections = ActiveCluster.getInstance().get().bucket(colsDesc.getBucket()).collections().getAllScopes().stream().filter(scope -> scope.name().equals(colsDesc.getScope())).flatMap(scope -> scope.collections().stream()).toList();

((ScopeNodeDescriptor) ((DefaultMutableTreeNode) parentNode.getParent()).getUserObject()).setCounter(formatCount(collections.size()));

List<CollectionSpec> collections = ActiveCluster.getInstance().get().bucket(scopeDesc.getBucket()).collections().getAllScopes().stream().filter(scope -> scope.name().equals(scopeDesc.getText())).flatMap(scope -> scope.collections().stream()).toList();
scopeDesc.setCounter(formatCount(collections.size()));
if (!collections.isEmpty()) {
for (CollectionSpec spec : collections) {

String filter = QueryFiltersStorage.getInstance().getValue().getQueryFilter(ActiveCluster.getInstance().getId(), scopeDesc.getBucket(), scopeDesc.getText(), spec.name());
String filter = QueryFiltersStorage.getInstance().getValue().getQueryFilter(ActiveCluster.getInstance().getId(), colsDesc.getBucket(), colsDesc.getScope(), spec.name());

CollectionNodeDescriptor colNodeDesc = new CollectionNodeDescriptor(spec.name(), ActiveCluster.getInstance().getId(), scopeDesc.getBucket(), scopeDesc.getText(), filter);
colNodeDesc.setCounter(formatCount(counts.get(scopeDesc.getBucket() + "." + scopeDesc.getText() + "." + spec.name())));
CollectionNodeDescriptor colNodeDesc = new CollectionNodeDescriptor(spec.name(), ActiveCluster.getInstance().getId(), colsDesc.getBucket(), colsDesc.getScope(), filter);
colNodeDesc.setCounter(formatCount(counts.get(colsDesc.getBucket() + "." + colsDesc.getScope() + "." + spec.name())));
DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(colNodeDesc);
childNode.add(new DefaultMutableTreeNode(new LoadingNodeDescriptor()));
parentNode.add(childNode);
Expand Down Expand Up @@ -648,4 +661,67 @@ public static String formatCount(Integer num) {
return String.format("%.2fM", num / 1000000.0);
}
}


public static void listSearchIndexes(DefaultMutableTreeNode parentNode, Tree tree) {
Object userObject = parentNode.getUserObject();
if (userObject instanceof SearchNodeDescriptor) {
CompletableFuture.runAsync(() -> {
tree.setPaintBusy(true);
parentNode.removeAllChildren();

try {
SearchNodeDescriptor searchDesc = (SearchNodeDescriptor) userObject;

SearchIndexManager topSearch = ActiveCluster.getInstance().get().searchIndexes();
List<SearchIndex> allIdx = topSearch.getAllIndexes();

List<SearchIndex> results = new ArrayList<>();

for (SearchIndex idx : allIdx) {
if (!searchDesc.getBucket().equals(idx.sourceName())) {
continue;
}

Map<String, Object> mapping = (Map<String, Object>) idx.params().get("mapping");
Map<String, Object> types = (Map<String, Object>) mapping.get("types");
Map<String, Object> docConfig = (Map<String, Object>) idx.params().get("doc_config");
String mode = docConfig.get("mode").toString();

if ("type_field".equals(mode) && "_default".equals(searchDesc.getScope())) {
results.add(idx);
} else if (!"type_field".equals(mode) && !types.keySet().stream()
.filter(e -> e.startsWith(searchDesc.getScope() + "."))
.collect(Collectors.toSet()).isEmpty()) {
results.add(idx);
}
}

if (!results.isEmpty()) {
for (SearchIndex searchIndex : results) {

String fileName = searchIndex.name() + ".json";
VirtualFile virtualFile = new LightVirtualFile(fileName, JsonFileType.INSTANCE, searchIndex.toJson());
virtualFile.putUserData(READ_ONLY, "true");

SearchIndexNodeDescriptor node = new SearchIndexNodeDescriptor(searchIndex.name(), searchDesc.getBucket(), searchDesc.getScope(), fileName, virtualFile);
DefaultMutableTreeNode jsonFileNode = new DefaultMutableTreeNode(node);
parentNode.add(jsonFileNode);
}
} else {
parentNode.add(new DefaultMutableTreeNode(new NoResultsNodeDescriptor()));
}
ApplicationManager.getApplication().invokeLater(() -> {
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(parentNode);
tree.setPaintBusy(false);
});

} catch (Exception e) {
Log.error(e);
} finally {
tree.setPaintBusy(false);
}
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.couchbase.intellij.searchworkbench;

import com.intellij.json.JsonLanguage;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.util.IconLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;

public class CBSJsonFileType extends LanguageFileType {
public static final CBSJsonFileType INSTANCE = new CBSJsonFileType();

public static final Icon FILE = IconLoader.getIcon("/assets/cbs.png", CBSJsonFileType.class);

private CBSJsonFileType() {
super(JsonLanguage.INSTANCE);
}

@NotNull
@Override
public String getName() {
return "Couchbase Search Query";
}

@NotNull
@Override
public String getDescription() {
return "JSON query for couchbase search";
}

@NotNull
@Override
public String getDefaultExtension() {
return "cbs.json";
}

@Nullable
@Override
public Icon getIcon() {
return FILE;
}
}
Loading

0 comments on commit 41ccad4

Please sign in to comment.