Skip to content

Commit

Permalink
feat: Better check for status of the "Runtime Anaylysis" step
Browse files Browse the repository at this point in the history
  • Loading branch information
jansorg authored and ahtrotta committed Nov 10, 2023
1 parent a61a9ee commit 7e5b29e
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.PathUtil;
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -19,4 +22,9 @@ public static boolean isFindingFile(@NotNull String path) {
public static boolean isFindingFile(@Nullable VirtualFile file) {
return file != null && FileUtil.fileNameEquals(file.getName(), FINDINGS_FILE_NAME);
}

@RequiresReadLock
public static boolean isAnalysisPerformed(GlobalSearchScope searchScope) {
return !FilenameIndex.getVirtualFilesByName(FINDINGS_FILE_NAME, searchScope).isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
import appland.installGuide.projectData.ProjectDataService;
import appland.installGuide.projectData.ProjectMetadata;
import appland.oauth.AppMapLoginAction;
import appland.problemsView.FindingsViewTab;
import appland.settings.AppMapApplicationSettingsService;
import appland.settings.AppMapProjectSettingsService;
import appland.settings.AppMapSettingsListener;
import appland.telemetry.TelemetryService;
import appland.webviews.WebviewEditor;
import appland.webviews.findings.FindingsOverviewEditorProvider;
import com.google.gson.Gson;
Expand Down Expand Up @@ -68,14 +66,23 @@ public InstallGuideEditor(@NotNull Project project,
this.currentPage = page;
}

public void navigateTo(@NotNull InstallGuideViewPage page, boolean postWebviewMessage) {
/**
* Navigate to the given page.
*
* @param page Target page
* @param postWebviewMessage If the webview should be instructed to navigate to the given page.
* @param fromWebview If the navigation originated from the webview
*/
public void navigateTo(@NotNull InstallGuideViewPage page, boolean postWebviewMessage, boolean fromWebview) {
this.currentPage = page;

if (page == InstallGuideViewPage.RuntimeAnalysis) {
AppMapProjectSettingsService.getState(project).setInvestigatedFindings(true);
var projectSettings = AppMapProjectSettingsService.getState(project);
if (fromWebview && page == InstallGuideViewPage.RuntimeAnalysis && projectSettings.isOpenedAppMapEditor()) {
projectSettings.setInvestigatedFindings(true);
}

if (postWebviewMessage) {
assert !fromWebview;
postMessage(createPageNavigationJSON(page));
}
}
Expand Down Expand Up @@ -217,7 +224,7 @@ protected void handleMessage(@NotNull String messageId, @Nullable JsonObject mes

@NotNull
private List<ProjectMetadata> findProjects() {
return ProjectDataService.getInstance(project).getAppMapProjects();
return ProjectDataService.getInstance(project).getAppMapProjects(true);
}

/**
Expand Down Expand Up @@ -275,7 +282,7 @@ private void handleMessageOpenFile(@NotNull JsonObject message) {
private void handleMessageOpenPage(@NotNull JsonObject message) {
// update state, which is based on the new page
var viewId = message.getAsJsonPrimitive("page").getAsString();
navigateTo(InstallGuideViewPage.findByPageId(viewId), false);
navigateTo(InstallGuideViewPage.findByPageId(viewId), false, true);
}

private void executeInstallCommand(String path, String language) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static void open(@NotNull Project project, @NotNull InstallGuideViewPage
assert editor instanceof InstallGuideEditor;

FileEditorManagerEx.getInstanceEx(project).openFile(file, true, true);
((InstallGuideEditor) editor).navigateTo(page, true);
((InstallGuideEditor) editor).navigateTo(page, true, false);
return;
}
}
Expand All @@ -45,7 +45,7 @@ public static void open(@NotNull Project project, @NotNull InstallGuideViewPage
var newEditors = editorManager.openFile(file, true);
if (newEditors.length == 1) {
// don't post webview message because the init message of the new editor was just sent
((InstallGuideEditor) newEditors[0]).navigateTo(page, false);
((InstallGuideEditor) newEditors[0]).navigateTo(page, false, false);
}
} finally {
// notify in a background thread because we don't want to delay opening the editor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScopes;
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import it.unimi.dsi.fastutil.ints.IntSet;
Expand Down Expand Up @@ -47,10 +46,11 @@ public DefaultProjectDataService(@NotNull Project project) {

@SuppressWarnings("UnstableApiUsage")
@Override
public @NotNull List<ProjectMetadata> getAppMapProjects() {
ApplicationManager.getApplication().assertReadAccessNotAllowed();

updateMetadata();
public @NotNull List<ProjectMetadata> getAppMapProjects(boolean updateMetadata) {
if (updateMetadata) {
ApplicationManager.getApplication().assertReadAccessNotAllowed();
updateMetadata();
}
return cachedProjects.get();
}

Expand Down Expand Up @@ -203,7 +203,7 @@ private List<SimpleCodeObject> truncateSampleCodeObjects(List<SimpleCodeObject>
@RequiresReadLock
private boolean isAnalysisPerformed(@NotNull VirtualFile root) {
var searchScope = GlobalSearchScopes.directoryScope(project, root, true);
return !FilenameIndex.getVirtualFilesByName(AppMapFindingsUtil.FINDINGS_FILE_NAME, searchScope).isEmpty();
return AppMapFindingsUtil.isAnalysisPerformed(searchScope);
}

private boolean isNodeSupported(@Nullable NodeVersion nodeVersion) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ public interface ProjectDataService {
return project.getService(ProjectDataService.class);
}

@NotNull List<ProjectMetadata> getAppMapProjects();
/**
* @param updateMetadata If the metadata should be updated before retuning the cached projects.
* If {@code true}, then this method MUST NOT be invoked in a ReadAction.
* @return The available AppMap projects
*/
@NotNull List<ProjectMetadata> getAppMapProjects(boolean updateMetadata);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package appland.toolwindow.installGuide;

import appland.config.AppMapConfigFileListener;
import appland.index.AppMapFindingsUtil;
import appland.index.AppMapNameIndex;
import appland.index.AppMapSearchScopes;
import appland.installGuide.InstallGuideViewPage;
Expand All @@ -11,18 +12,19 @@
import appland.settings.AppMapSettingsListener;
import appland.toolwindow.AppMapContentPanel;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.Alarm;
import com.intellij.util.SingleAlarm;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

Expand All @@ -35,7 +37,7 @@ public class InstallGuidePanel extends AppMapContentPanel implements Disposable
private final Project project;
private final List<StatusLabel> statusLabels;
// debounce label updates by 500ms
private final SingleAlarm labelRefreshAlarm = new SingleAlarm(this::refreshInitialStatus, 500, this, Alarm.ThreadToUse.SWING_THREAD);
private final SingleAlarm labelRefreshAlarm = new SingleAlarm(this::refreshInitialStatus, 500, this, Alarm.ThreadToUse.POOLED_THREAD);

public InstallGuidePanel(@NotNull Project project, @NotNull Disposable parent) {
super(false);
Expand All @@ -48,92 +50,107 @@ public InstallGuidePanel(@NotNull Project project, @NotNull Disposable parent) {
setupPanel();
}

@Override
public void dispose() {
}

@Override
protected void setupPanel() {
statusLabels.forEach(this::add);
refreshInitialStatus();
registerStatusUpdateListeners(project, this, statusLabels);
labelRefreshAlarm.request(true);
}

private void triggerLabelStatusUpdate() {
labelRefreshAlarm.cancelAndRequest();
}

@RequiresBackgroundThread
private void refreshInitialStatus() {
for (var label : statusLabels) {
updateLabelStatus(project, label);
}
// Force a refresh of the metadata outside the non-blocking ReadAction below
// The refresh must not be executed within a ReadAction.
ProjectDataService.getInstance(project).getAppMapProjects(true);

ReadAction.nonBlocking(() -> {
return statusLabels.stream().collect(Collectors.toMap(Function.identity(), label -> {
return updateLabelStatus(project, label);
}));
})
.coalesceBy(this)
.expireWith(this)
.inSmartMode(project)
.finishOnUiThread(ModalityState.any(), statusMapping -> {
for (var entry : statusMapping.entrySet()) {
entry.getKey().setStatus(entry.getValue());
}
}
)
.submit(AppExecutorUtil.getAppExecutorService());
}

/**
* if findings are enabled, completed if at least one appmap-findings.json file was found
* @param project Current project
* @param label Label to check
* @return {@code true} if the label should be enabled, {@code false} if it should be disabled
*/
private static void updateRuntimeAnalysisLabel(@NotNull Project project, @NotNull StatusLabel label) {
var hasFindings = AppMapProjectSettingsService.getState(project).isInvestigatedFindings();
label.setStatus(hasFindings ? InstallGuideStatus.Completed : InstallGuideStatus.Incomplete);
}

private void updateLabelStatus(@NotNull Project project, @NotNull StatusLabel label) {
private @NotNull InstallGuideStatus updateLabelStatus(@NotNull Project project, @NotNull StatusLabel label) {
switch (label.getPage()) {
case InstallAgent:
updateInstallAgentLabel(project, label);
break;

return updateInstallAgentLabel(project);
case RecordAppMaps:
updateRecordAppMapsLabel(project, label);
break;

return updateRecordAppMapsLabel(project);
case OpenAppMaps:
updateOpenAppMapsLabel(project, label);
break;

return updateOpenAppMapsLabel(project);
case RuntimeAnalysis:
updateRuntimeAnalysisLabel(project, label);
break;
return updateRuntimeAnalysisLabel(project);
default:
throw new IllegalStateException("unexpected label: " + label);
}
}

@Override
public void dispose() {
}

/**
* Presence of at least one appmap.yml file.
*/
private static void updateInstallAgentLabel(@NotNull Project project, @NotNull StatusLabel label) {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
// Always mark supported Java projects as supported, but only if all modules are supported.
// isAgentInstalled is already checking for supported Java projects and the presence of appmap.yml.
var anySupported = ProjectDataService.getInstance(project).getAppMapProjects()
.stream()
.anyMatch(ProjectMetadata::isAgentInstalled);

ApplicationManager.getApplication().invokeLater(() -> {
label.setStatus(anySupported ? InstallGuideStatus.Completed : InstallGuideStatus.Incomplete);
});
});
private static @NotNull InstallGuideStatus updateInstallAgentLabel(@NotNull Project project) {
// Always mark supported Java projects as supported, but only if all modules are supported.
// isAgentInstalled is already checking for supported Java projects and the presence of appmap.yml.
var anySupported = ProjectDataService.getInstance(project).getAppMapProjects(false)
.stream()
.anyMatch(ProjectMetadata::isAgentInstalled);
return anySupported ? InstallGuideStatus.Completed : InstallGuideStatus.Incomplete;
}

/**
* presence of at least one .appmap.json file
* Presence of at least one .appmap.json file.
*/
private static void updateRecordAppMapsLabel(@NotNull Project project, @NotNull StatusLabel label) {
findIndexedStatus(project, label, () -> !AppMapNameIndex.isEmpty(project, AppMapSearchScopes.appMapsWithExcluded(project)));
private static @NotNull InstallGuideStatus updateRecordAppMapsLabel(@NotNull Project project) {
return !AppMapNameIndex.isEmpty(project, AppMapSearchScopes.appMapsWithExcluded(project))
? InstallGuideStatus.Completed
: InstallGuideStatus.Incomplete;
}

/**
* if the AppMap webview was at least shown once
* If the AppMap webview was at least shown once
*/
private static void updateOpenAppMapsLabel(@NotNull Project project, @NotNull StatusLabel label) {
label.setStatus(AppMapProjectSettingsService.getState(project).isOpenedAppMapEditor()
private static @NotNull InstallGuideStatus updateOpenAppMapsLabel(@NotNull Project project) {
return AppMapProjectSettingsService.getState(project).isOpenedAppMapEditor()
? InstallGuideStatus.Completed
: InstallGuideStatus.Incomplete);
: InstallGuideStatus.Incomplete;
}

private static void updateGenerateOpenApiLabel(@NotNull Project project, @NotNull StatusLabel label) {
label.setStatus(AppMapProjectSettingsService.getState(project).isCreatedOpenAPI()
/**
* The label is updated under the following conditions:
* - The user navigated to the "Runtime Analysis" step in the installation guide webview
* - AppMaps have been opened
* - There are AppMaps in the project, and they have been scanned, regardless if findings were detected
*/
private static @NotNull InstallGuideStatus updateRuntimeAnalysisLabel(@NotNull Project project) {
var navigated = AppMapProjectSettingsService.getState(project).isInvestigatedFindings();
var openedAppMaps = AppMapProjectSettingsService.getState(project).isOpenedAppMapEditor();
var scannedAppMaps = AppMapFindingsUtil.isAnalysisPerformed(AppMapSearchScopes.projectFilesWithExcluded(project));
return navigated && openedAppMaps && scannedAppMaps
? InstallGuideStatus.Completed
: InstallGuideStatus.Incomplete);
: InstallGuideStatus.Incomplete;
}

private void registerStatusUpdateListeners(@NotNull Project project,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void projectA() {
// copy into src, which is the only content root of the test project
WriteAction.runAndWait(() -> myFixture.copyDirectoryToProject("project-a", ""));

var projects = ProjectDataService.getInstance(getProject()).getAppMapProjects();
var projects = ProjectDataService.getInstance(getProject()).getAppMapProjects(true);
Assert.assertEquals(1, projects.size());

var projectA = projects.get(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ protected boolean runInDispatchThread() {
public void selectedSampleObjects() {
WriteAction.runAndWait(() -> myFixture.copyDirectoryToProject("appmap-scanner/with_findings", "root"));

var projects = ProjectDataService.getInstance(getProject()).getAppMapProjects();
var projects = ProjectDataService.getInstance(getProject()).getAppMapProjects(true);
assertEquals(1, projects.size());

var samples = projects.get(0).sampleCodeObjects;
Expand Down Expand Up @@ -43,7 +43,7 @@ public void selectedSampleObjects() {
public void truncatedSampleObjects() {
WriteAction.runAndWait(() -> myFixture.copyDirectoryToProject("appmap-scanner/untruncated_findings", "root"));

var projects = ProjectDataService.getInstance(getProject()).getAppMapProjects();
var projects = ProjectDataService.getInstance(getProject()).getAppMapProjects(true);
assertEquals(1, projects.size());

var samples = projects.get(0).sampleCodeObjects;
Expand Down

0 comments on commit 7e5b29e

Please sign in to comment.